nlm-memory 0.12.0 → 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 (125) 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 +314 -55
  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-facts/fact-recall-service.js +75 -28
  31. package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
  32. package/dist/core/scheduler/scheduler.d.ts +4 -2
  33. package/dist/core/scheduler/scheduler.js +26 -15
  34. package/dist/core/scheduler/scheduler.js.map +1 -1
  35. package/dist/core/sources/source-registry.d.ts +36 -12
  36. package/dist/core/sources/source-registry.js +59 -44
  37. package/dist/core/sources/source-registry.js.map +1 -1
  38. package/dist/core/storage/backup-rotation.d.ts +38 -0
  39. package/dist/core/storage/backup-rotation.js +82 -0
  40. package/dist/core/storage/backup-rotation.js.map +1 -0
  41. package/dist/core/storage/null-code-exemplar-store.d.ts +21 -0
  42. package/dist/core/storage/null-code-exemplar-store.js +19 -0
  43. package/dist/core/storage/null-code-exemplar-store.js.map +1 -0
  44. package/dist/core/storage/pg-fact-store.d.ts +2 -0
  45. package/dist/core/storage/pg-fact-store.js +34 -3
  46. package/dist/core/storage/pg-fact-store.js.map +1 -1
  47. package/dist/core/storage/pg-session-store.d.ts +14 -4
  48. package/dist/core/storage/pg-session-store.js +123 -2
  49. package/dist/core/storage/pg-session-store.js.map +1 -1
  50. package/dist/core/storage/pg-storage.d.ts +11 -2
  51. package/dist/core/storage/pg-storage.js +14 -2
  52. package/dist/core/storage/pg-storage.js.map +1 -1
  53. package/dist/core/storage/pg-tx-context.d.ts +2 -0
  54. package/dist/core/storage/pg-tx-context.js +12 -0
  55. package/dist/core/storage/pg-tx-context.js.map +1 -1
  56. package/dist/core/storage/sqlite-code-exemplar-store.d.ts +40 -0
  57. package/dist/core/storage/sqlite-code-exemplar-store.js +247 -0
  58. package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -0
  59. package/dist/core/storage/sqlite-fact-store.d.ts +2 -0
  60. package/dist/core/storage/sqlite-fact-store.js +30 -2
  61. package/dist/core/storage/sqlite-fact-store.js.map +1 -1
  62. package/dist/core/storage/sqlite-session-store.d.ts +21 -0
  63. package/dist/core/storage/sqlite-session-store.js +23 -0
  64. package/dist/core/storage/sqlite-session-store.js.map +1 -1
  65. package/dist/core/storage/sqlite-storage.d.ts +6 -0
  66. package/dist/core/storage/sqlite-storage.js +14 -2
  67. package/dist/core/storage/sqlite-storage.js.map +1 -1
  68. package/dist/core/storage/supersedence-log.d.ts +8 -0
  69. package/dist/core/storage/supersedence-log.js +19 -0
  70. package/dist/core/storage/supersedence-log.js.map +1 -1
  71. package/dist/hook/prompt-recall-hook.d.ts +5 -4
  72. package/dist/hook/prompt-recall-hook.js +12 -7
  73. package/dist/hook/prompt-recall-hook.js.map +1 -1
  74. package/dist/http/app.d.ts +7 -1
  75. package/dist/http/app.js +226 -83
  76. package/dist/http/app.js.map +1 -1
  77. package/dist/install/codex.d.ts +29 -1
  78. package/dist/install/codex.js +117 -15
  79. package/dist/install/codex.js.map +1 -1
  80. package/dist/install/cursor.d.ts +4 -4
  81. package/dist/install/cursor.js +7 -7
  82. package/dist/install/cursor.js.map +1 -1
  83. package/dist/install/health.d.ts +56 -0
  84. package/dist/install/health.js +133 -0
  85. package/dist/install/health.js.map +1 -0
  86. package/dist/install/setup.js +1 -1
  87. package/dist/install/setup.js.map +1 -1
  88. package/dist/install/windsurf.d.ts +4 -4
  89. package/dist/install/windsurf.js +7 -7
  90. package/dist/install/windsurf.js.map +1 -1
  91. package/dist/llm/classifier-box.d.ts +4 -0
  92. package/dist/llm/classifier-box.js +9 -2
  93. package/dist/llm/classifier-box.js.map +1 -1
  94. package/dist/llm/ollama-client.d.ts +19 -1
  95. package/dist/llm/ollama-client.js +14 -6
  96. package/dist/llm/ollama-client.js.map +1 -1
  97. package/dist/llm/ollama-code-embedder.d.ts +28 -0
  98. package/dist/llm/ollama-code-embedder.js +73 -0
  99. package/dist/llm/ollama-code-embedder.js.map +1 -0
  100. package/dist/mcp/server.d.ts +10 -0
  101. package/dist/mcp/server.js +132 -1
  102. package/dist/mcp/server.js.map +1 -1
  103. package/dist/ports/code-embedder.d.ts +27 -0
  104. package/dist/ports/code-embedder.js +15 -0
  105. package/dist/ports/code-embedder.js.map +1 -0
  106. package/dist/ports/code-exemplar-store.d.ts +45 -0
  107. package/dist/ports/code-exemplar-store.js +11 -0
  108. package/dist/ports/code-exemplar-store.js.map +1 -0
  109. package/dist/ports/fact-store.d.ts +0 -0
  110. package/dist/ports/fact-store.js.map +1 -1
  111. package/dist/ports/storage.d.ts +8 -0
  112. package/dist/shared/types.d.ts +35 -0
  113. package/dist/ui/assets/{index-Fr5AMEPj.js → index-CVo7KKw2.js} +1 -1
  114. package/dist/ui/assets/{index-C74N8APQ.css → index-DCuQrzJH.css} +1 -1
  115. package/dist/ui/index.html +2 -2
  116. package/migrations/021_code_exemplars.sql +51 -0
  117. package/migrations/022_facts_retired_at.sql +17 -0
  118. package/migrations/pg/021_code_exemplars.sql +45 -0
  119. package/migrations/pg/022_facts_retired_at.sql +12 -0
  120. package/nlm/index.js +6 -1
  121. package/package.json +3 -2
  122. package/plugin/.codex-plugin/plugin.json +2 -2
  123. package/plugin/hooks/hooks.json +13 -2
  124. package/plugin/scripts/prompt-recall-hook.mjs +7 -1
  125. 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":"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"}
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)")
@@ -480,11 +531,11 @@ program
480
531
  .description("Run TS classifier against ~/.nlm/canonical.sqlite and diff vs persisted Python output")
481
532
  .option("-l, --limit <n>", "sessions to sample", (v) => Number.parseInt(v, 10), 10)
482
533
  .option("-p, --provider <name>", "deepseek | ollama", "deepseek")
483
- .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)")
484
535
  .option("-v, --verbose", "per-session diff lines on stderr")
485
536
  .action(async (opts) => {
486
537
  const provider = opts.provider === "ollama" ? "ollama" : "deepseek";
487
- 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";
488
539
  const report = await runParity({
489
540
  limit: opts.limit,
490
541
  dbPath: dbPath(),
@@ -532,8 +583,7 @@ program
532
583
  const { storage, store, facts, embedder, classifier } = await buildStack();
533
584
  try {
534
585
  const report = await backfillFacts({
535
- // TODO(#215a): PgStorage backfill port; SQLite-only until then
536
- store: store,
586
+ store,
537
587
  factStore: facts,
538
588
  classifier,
539
589
  embedder: opts.embed === false ? null : embedder,
@@ -576,8 +626,16 @@ program
576
626
  .command("mcp")
577
627
  .description("Run as an MCP stdio server (for ~/.mcp.json)")
578
628
  .action(async () => {
579
- const { recall, store, facts, factRecall } = await buildStack();
580
- 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
+ });
581
639
  const transport = new StdioServerTransport();
582
640
  await server.connect(transport);
583
641
  });
@@ -1029,6 +1087,7 @@ connect
1029
1087
  .option("--source <source>", "marketplace source (owner/repo, git URL, or local path)", "pbmagnet4/nlm-memory")
1030
1088
  .option("--local", "shortcut for --source <repo-root>; use during dev")
1031
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")
1032
1091
  .option("--dry-run", "print what would happen without invoking codex")
1033
1092
  .action((opts) => {
1034
1093
  if (!opts.dryRun && !codexBinaryAvailable()) {
@@ -1036,8 +1095,19 @@ connect
1036
1095
  process.exit(1);
1037
1096
  }
1038
1097
  const source = opts.local ? REPO_ROOT : opts.source;
1039
- 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
+ }
1040
1108
  if (report.dryRun) {
1109
+ if (repair)
1110
+ console.error(" --repair: would strip any stale nlm-memory-ts block, then:");
1041
1111
  console.error("nlm connect codex (dry run):");
1042
1112
  console.error(` codex plugin marketplace add ${report.source}`);
1043
1113
  console.error(` codex plugin add ${report.pluginName}@${report.marketplaceName}`);
@@ -1065,6 +1135,9 @@ connect
1065
1135
  if (report.mcpServerWritten) {
1066
1136
  console.error(` Wrote [mcp_servers.nlm-memory] to ${report.mcpServerWritten}`);
1067
1137
  }
1138
+ else if (report.mcpServerAlreadyPresent) {
1139
+ console.error(" Left your existing [mcp_servers.nlm-memory] block in place (not overwriting it)");
1140
+ }
1068
1141
  if (report.legacyHooksWritten) {
1069
1142
  console.error(` Wrote hooks.json fallback to ${report.legacyHooksWritten}`);
1070
1143
  }
@@ -1150,9 +1223,7 @@ connect
1150
1223
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1151
1224
  await storage.init();
1152
1225
  try {
1153
- // TODO(#215a): replace storage.rawDb() with port methods
1154
- const registry = new SourceRegistry(storage.rawDb());
1155
- const report = connectCursor(registry, {
1226
+ const report = await connectCursor(storage.sources, {
1156
1227
  ...(opts.dbPath ? { dbPath: opts.dbPath } : {}),
1157
1228
  dryRun: Boolean(opts.dryRun),
1158
1229
  });
@@ -1184,9 +1255,7 @@ connect
1184
1255
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1185
1256
  await storage.init();
1186
1257
  try {
1187
- // TODO(#215a): replace storage.rawDb() with port methods
1188
- const registry = new SourceRegistry(storage.rawDb());
1189
- const report = connectWindsurf(registry, {
1258
+ const report = await connectWindsurf(storage.sources, {
1190
1259
  ...(opts.userDir ? { userDir: opts.userDir } : {}),
1191
1260
  dryRun: Boolean(opts.dryRun),
1192
1261
  });
@@ -1349,9 +1418,7 @@ disconnect
1349
1418
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1350
1419
  await storage.init();
1351
1420
  try {
1352
- // TODO(#215a): replace storage.rawDb() with port methods
1353
- const registry = new SourceRegistry(storage.rawDb());
1354
- const report = disconnectCursor(registry, { dryRun: Boolean(opts.dryRun) });
1421
+ const report = await disconnectCursor(storage.sources, { dryRun: Boolean(opts.dryRun) });
1355
1422
  if (opts.dryRun) {
1356
1423
  console.error("nlm disconnect cursor (dry run): disable Cursor source in registry");
1357
1424
  if (opts.withRules)
@@ -1379,9 +1446,7 @@ disconnect
1379
1446
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1380
1447
  await storage.init();
1381
1448
  try {
1382
- // TODO(#215a): replace storage.rawDb() with port methods
1383
- const registry = new SourceRegistry(storage.rawDb());
1384
- const report = disconnectWindsurf(registry, { dryRun: Boolean(opts.dryRun) });
1449
+ const report = await disconnectWindsurf(storage.sources, { dryRun: Boolean(opts.dryRun) });
1385
1450
  if (opts.dryRun) {
1386
1451
  console.error("nlm disconnect windsurf (dry run): disable Windsurf source in registry");
1387
1452
  if (opts.withRules)
@@ -1498,9 +1563,69 @@ program
1498
1563
  process.exit(1);
1499
1564
  }
1500
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
+ }
1501
1626
  program
1502
1627
  .command("doctor")
1503
- .description("Check database integrity invariants and optionally repair safe violations")
1628
+ .description("Check database integrity invariants and install/runtime health, optionally repair safe violations")
1504
1629
  .option("--fix", "repair mechanically safe violations: delete self-loop edges (I1), restore orphaned superseded/replaced sessions to closed (I2)")
1505
1630
  .action(async (opts) => {
1506
1631
  const storage = await buildStorage(dbPath());
@@ -1537,6 +1662,7 @@ program
1537
1662
  const ALL_CHECKS = ["I1", "I2", "I3", "I4", "I5a", "I5b", "I6"];
1538
1663
  const byId = new Map(violations.map((v) => [v.id, v]));
1539
1664
  let anyFail = false;
1665
+ console.log("Database integrity:");
1540
1666
  for (const id of ALL_CHECKS) {
1541
1667
  const v = byId.get(id);
1542
1668
  if (v) {
@@ -1548,9 +1674,142 @@ program
1548
1674
  console.log(`PASS ${id}`);
1549
1675
  }
1550
1676
  }
1677
+ console.log("\nInstall & runtime health:");
1678
+ if (printHealthChecks(evaluateInstallHealth(await gatherInstallProbe())))
1679
+ anyFail = true;
1551
1680
  if (anyFail)
1552
1681
  process.exit(1);
1553
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");
1748
+ if (anyFail)
1749
+ process.exit(1);
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
+ });
1554
1813
  program.parseAsync().catch((e) => {
1555
1814
  console.error("nlm: fatal", e);
1556
1815
  process.exit(1);