@pcircle/memesh 4.8.5 → 4.9.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 (226) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/mcp.json +6 -4
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/AGENTS.md +60 -14
  6. package/README.de.md +83 -556
  7. package/README.md +83 -582
  8. package/README.zh-TW.md +84 -573
  9. package/dashboard/dist/index.html +10 -10
  10. package/dist/cli/view-live.d.ts.map +1 -1
  11. package/dist/cli/view-live.js +154 -384
  12. package/dist/cli/view-live.js.map +1 -1
  13. package/dist/core/agent-router.d.ts +11 -10
  14. package/dist/core/agent-router.d.ts.map +1 -1
  15. package/dist/core/agent-router.js +29 -14
  16. package/dist/core/agent-router.js.map +1 -1
  17. package/dist/core/agent-scope-id.d.ts.map +1 -1
  18. package/dist/core/agent-scope-id.js.map +1 -1
  19. package/dist/core/analytics.d.ts.map +1 -1
  20. package/dist/core/analytics.js.map +1 -1
  21. package/dist/core/config.d.ts +4 -40
  22. package/dist/core/config.d.ts.map +1 -1
  23. package/dist/core/config.js +75 -141
  24. package/dist/core/config.js.map +1 -1
  25. package/dist/core/demo.d.ts.map +1 -1
  26. package/dist/core/demo.js +6 -6
  27. package/dist/core/demo.js.map +1 -1
  28. package/dist/core/doctor.d.ts +1 -6
  29. package/dist/core/doctor.d.ts.map +1 -1
  30. package/dist/core/doctor.js +55 -227
  31. package/dist/core/doctor.js.map +1 -1
  32. package/dist/core/dreamer.d.ts +32 -47
  33. package/dist/core/dreamer.d.ts.map +1 -1
  34. package/dist/core/dreamer.js +211 -703
  35. package/dist/core/dreamer.js.map +1 -1
  36. package/dist/core/install-channel.d.ts.map +1 -1
  37. package/dist/core/install-channel.js +4 -47
  38. package/dist/core/install-channel.js.map +1 -1
  39. package/dist/core/install-id.d.ts.map +1 -1
  40. package/dist/core/install-id.js.map +1 -1
  41. package/dist/core/kg-backfill.d.ts.map +1 -1
  42. package/dist/core/kg-backfill.js.map +1 -1
  43. package/dist/core/lesson-engine.d.ts +0 -5
  44. package/dist/core/lesson-engine.d.ts.map +1 -1
  45. package/dist/core/lesson-engine.js +0 -25
  46. package/dist/core/lesson-engine.js.map +1 -1
  47. package/dist/core/lifecycle.d.ts.map +1 -1
  48. package/dist/core/lifecycle.js +54 -48
  49. package/dist/core/lifecycle.js.map +1 -1
  50. package/dist/core/memory-tool.d.ts.map +1 -1
  51. package/dist/core/memory-tool.js +17 -17
  52. package/dist/core/memory-tool.js.map +1 -1
  53. package/dist/core/operations.d.ts +2 -26
  54. package/dist/core/operations.d.ts.map +1 -1
  55. package/dist/core/operations.js +9 -244
  56. package/dist/core/operations.js.map +1 -1
  57. package/dist/core/paths.d.ts +4 -1
  58. package/dist/core/paths.d.ts.map +1 -1
  59. package/dist/core/paths.js +88 -14
  60. package/dist/core/paths.js.map +1 -1
  61. package/dist/core/product-improvements.js +2 -2
  62. package/dist/core/product-improvements.js.map +1 -1
  63. package/dist/core/schema-export.d.ts.map +1 -1
  64. package/dist/core/schema-export.js +10 -0
  65. package/dist/core/schema-export.js.map +1 -1
  66. package/dist/core/semver.d.ts +7 -0
  67. package/dist/core/semver.d.ts.map +1 -0
  68. package/dist/core/semver.js +49 -0
  69. package/dist/core/semver.js.map +1 -0
  70. package/dist/core/serializer.d.ts.map +1 -1
  71. package/dist/core/serializer.js +69 -57
  72. package/dist/core/serializer.js.map +1 -1
  73. package/dist/core/signal-scorer.d.ts.map +1 -1
  74. package/dist/core/signal-scorer.js.map +1 -1
  75. package/dist/core/transcript-extractor.d.ts +1 -85
  76. package/dist/core/transcript-extractor.d.ts.map +1 -1
  77. package/dist/core/transcript-extractor.js +5 -364
  78. package/dist/core/transcript-extractor.js.map +1 -1
  79. package/dist/core/transcript-source.d.ts +22 -6
  80. package/dist/core/transcript-source.d.ts.map +1 -1
  81. package/dist/core/transcript-source.js +108 -69
  82. package/dist/core/transcript-source.js.map +1 -1
  83. package/dist/core/types.d.ts +1 -17
  84. package/dist/core/types.d.ts.map +1 -1
  85. package/dist/core/version-check.d.ts.map +1 -1
  86. package/dist/core/version-check.js +11 -1
  87. package/dist/core/version-check.js.map +1 -1
  88. package/dist/db.d.ts +0 -34
  89. package/dist/db.d.ts.map +1 -1
  90. package/dist/db.js +3 -287
  91. package/dist/db.js.map +1 -1
  92. package/dist/host-runtime/acp.d.ts.map +1 -1
  93. package/dist/host-runtime/acp.js +2 -2
  94. package/dist/host-runtime/acp.js.map +1 -1
  95. package/dist/host-runtime/claude.d.ts.map +1 -1
  96. package/dist/host-runtime/claude.js +9 -7
  97. package/dist/host-runtime/claude.js.map +1 -1
  98. package/dist/host-runtime/codex-session.d.ts +9 -1
  99. package/dist/host-runtime/codex-session.d.ts.map +1 -1
  100. package/dist/host-runtime/codex-session.js +474 -29
  101. package/dist/host-runtime/codex-session.js.map +1 -1
  102. package/dist/host-runtime/codex.d.ts.map +1 -1
  103. package/dist/host-runtime/codex.js +2 -2
  104. package/dist/host-runtime/codex.js.map +1 -1
  105. package/dist/host-runtime/config.d.ts +1 -0
  106. package/dist/host-runtime/config.d.ts.map +1 -1
  107. package/dist/host-runtime/config.js +4 -0
  108. package/dist/host-runtime/config.js.map +1 -1
  109. package/dist/host-runtime/router-client.d.ts.map +1 -1
  110. package/dist/host-runtime/router-client.js +62 -15
  111. package/dist/host-runtime/router-client.js.map +1 -1
  112. package/dist/host-runtime/router.js +2 -2
  113. package/dist/host-runtime/router.js.map +1 -1
  114. package/dist/knowledge-graph.d.ts.map +1 -1
  115. package/dist/knowledge-graph.js +54 -45
  116. package/dist/knowledge-graph.js.map +1 -1
  117. package/dist/mcp/THIRD_PARTY_NOTICES.txt +217 -0
  118. package/dist/mcp/server.js +30685 -38
  119. package/dist/mcp/server.js.map +6 -1
  120. package/dist/skills-manifest.json +22 -22
  121. package/dist/storage/entity-index.d.ts +0 -1
  122. package/dist/storage/entity-index.d.ts.map +1 -1
  123. package/dist/storage/entity-index.js +0 -7
  124. package/dist/storage/entity-index.js.map +1 -1
  125. package/dist/storage/fts-index.d.ts.map +1 -1
  126. package/dist/storage/fts-index.js +6 -7
  127. package/dist/storage/fts-index.js.map +1 -1
  128. package/dist/storage/graph-repairs.d.ts +0 -5
  129. package/dist/storage/graph-repairs.d.ts.map +1 -1
  130. package/dist/storage/graph-repairs.js +4 -91
  131. package/dist/storage/graph-repairs.js.map +1 -1
  132. package/dist/storage/schema.d.ts +1 -1
  133. package/dist/storage/schema.d.ts.map +1 -1
  134. package/dist/storage/schema.js +1 -2
  135. package/dist/storage/schema.js.map +1 -1
  136. package/dist/storage/sqlite.d.ts +0 -1
  137. package/dist/storage/sqlite.d.ts.map +1 -1
  138. package/dist/storage/sqlite.js.map +1 -1
  139. package/dist/transports/agent-messaging.d.ts.map +1 -1
  140. package/dist/transports/agent-messaging.js +5 -7
  141. package/dist/transports/agent-messaging.js.map +1 -1
  142. package/dist/transports/cli/cli.d.ts.map +1 -1
  143. package/dist/transports/cli/cli.js +52 -674
  144. package/dist/transports/cli/cli.js.map +1 -1
  145. package/dist/transports/http/retired-routes.js +1 -1
  146. package/dist/transports/http/retired-routes.js.map +1 -1
  147. package/dist/transports/http/server.d.ts.map +1 -1
  148. package/dist/transports/http/server.js +13 -232
  149. package/dist/transports/http/server.js.map +1 -1
  150. package/dist/transports/mcp/handlers.d.ts +127 -1
  151. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  152. package/dist/transports/mcp/handlers.js +53 -4
  153. package/dist/transports/mcp/handlers.js.map +1 -1
  154. package/dist/transports/schemas.d.ts +52 -0
  155. package/dist/transports/schemas.d.ts.map +1 -1
  156. package/dist/transports/schemas.js +39 -0
  157. package/dist/transports/schemas.js.map +1 -1
  158. package/docs/platforms/README.md +5 -4
  159. package/docs/platforms/agent-messaging.md +124 -60
  160. package/hooks/hooks.json +13 -2
  161. package/llms-install.md +62 -30
  162. package/package.json +4 -5
  163. package/scripts/hooks/_generated/core-paths.js +88 -14
  164. package/scripts/hooks/_generated/fts-index.js +6 -7
  165. package/scripts/hooks/_generated/schema.js +1 -2
  166. package/scripts/hooks/_shared.js +14 -20
  167. package/scripts/hooks/session-start.js +8 -6
  168. package/scripts/hooks/session-summary.js +19 -374
  169. package/skills/memesh/SKILL.md +19 -14
  170. package/skills/memesh-review/SKILL.md +7 -6
  171. package/dist/core/auto-tagger.d.ts +0 -10
  172. package/dist/core/auto-tagger.d.ts.map +0 -1
  173. package/dist/core/auto-tagger.js +0 -63
  174. package/dist/core/auto-tagger.js.map +0 -1
  175. package/dist/core/conflict-candidates.d.ts +0 -20
  176. package/dist/core/conflict-candidates.d.ts.map +0 -1
  177. package/dist/core/conflict-candidates.js +0 -71
  178. package/dist/core/conflict-candidates.js.map +0 -1
  179. package/dist/core/conflict-judge.d.ts +0 -58
  180. package/dist/core/conflict-judge.d.ts.map +0 -1
  181. package/dist/core/conflict-judge.js +0 -189
  182. package/dist/core/conflict-judge.js.map +0 -1
  183. package/dist/core/digest-validator.d.ts +0 -18
  184. package/dist/core/digest-validator.d.ts.map +0 -1
  185. package/dist/core/digest-validator.js +0 -85
  186. package/dist/core/digest-validator.js.map +0 -1
  187. package/dist/core/embedder.d.ts +0 -20
  188. package/dist/core/embedder.d.ts.map +0 -1
  189. package/dist/core/embedder.js +0 -245
  190. package/dist/core/embedder.js.map +0 -1
  191. package/dist/core/failure-analyzer.d.ts +0 -19
  192. package/dist/core/failure-analyzer.d.ts.map +0 -1
  193. package/dist/core/failure-analyzer.js +0 -83
  194. package/dist/core/failure-analyzer.js.map +0 -1
  195. package/dist/core/json-utils.d.ts +0 -3
  196. package/dist/core/json-utils.d.ts.map +0 -1
  197. package/dist/core/json-utils.js +0 -46
  198. package/dist/core/json-utils.js.map +0 -1
  199. package/dist/core/llm-client.d.ts +0 -22
  200. package/dist/core/llm-client.d.ts.map +0 -1
  201. package/dist/core/llm-client.js +0 -203
  202. package/dist/core/llm-client.js.map +0 -1
  203. package/dist/core/llm-telemetry.d.ts +0 -47
  204. package/dist/core/llm-telemetry.d.ts.map +0 -1
  205. package/dist/core/llm-telemetry.js +0 -117
  206. package/dist/core/llm-telemetry.js.map +0 -1
  207. package/dist/core/llm-validator.d.ts +0 -20
  208. package/dist/core/llm-validator.d.ts.map +0 -1
  209. package/dist/core/llm-validator.js +0 -231
  210. package/dist/core/llm-validator.js.map +0 -1
  211. package/dist/core/ollama-host.d.ts +0 -6
  212. package/dist/core/ollama-host.d.ts.map +0 -1
  213. package/dist/core/ollama-host.js +0 -62
  214. package/dist/core/ollama-host.js.map +0 -1
  215. package/dist/core/output-language.d.ts +0 -6
  216. package/dist/core/output-language.d.ts.map +0 -1
  217. package/dist/core/output-language.js +0 -25
  218. package/dist/core/output-language.js.map +0 -1
  219. package/dist/core/prompt-safety.d.ts +0 -4
  220. package/dist/core/prompt-safety.d.ts.map +0 -1
  221. package/dist/core/prompt-safety.js +0 -20
  222. package/dist/core/prompt-safety.js.map +0 -1
  223. package/dist/storage/vector-index.d.ts +0 -3
  224. package/dist/storage/vector-index.d.ts.map +0 -1
  225. package/dist/storage/vector-index.js +0 -13
  226. package/dist/storage/vector-index.js.map +0 -1
@@ -1,16 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { Command, Option } from 'commander';
2
+ import { Command } from 'commander';
3
3
  import { randomBytes } from 'crypto';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
- import { openDatabase, closeDatabase, getDatabase, reindexFts, readVectorGeneration, generationRowIds, discardVectorGeneration, } from '../../db.js';
8
- import { remember, recallWithConflicts, forget, exportMemories, importMemories, learn, reindex, setPinned } from '../../core/operations.js';
9
- import { readConfig, writeConfig, maskApiKey, detectCapabilities } from '../../core/config.js';
10
- import { MAX_LANGUAGE_LENGTH, languageValueError } from '../../core/output-language.js';
11
- import { getDbPath, getProjectName, homeDir, redactSecrets, redactUserPaths } from '../../core/paths.js';
7
+ import { openDatabase, closeDatabase, getDatabase, reindexFts, } from '../../db.js';
8
+ import { remember, recallWithConflicts, forget, exportMemories, importMemories, learn, setPinned } from '../../core/operations.js';
9
+ import { readConfig, updateConfig } from '../../core/config.js';
10
+ import { getAgentRouterSocketPath, getDbPath, getProjectName, homeDir, redactSecrets, redactUserPaths } from '../../core/paths.js';
12
11
  import { agentScopeIdRejection, canonicalAgentScopeId } from '../../core/agent-scope-id.js';
13
- import { flushPendingEmbeddings, canRefillVectorIndex } from '../../core/embedder.js';
14
12
  import { NAMESPACES } from '../../core/types.js';
15
13
  import { assembleBriefing } from '../../core/briefing.js';
16
14
  import { inspectHosts, allWired } from '../../core/setup.js';
@@ -37,7 +35,6 @@ async function withDatabase(fn) {
37
35
  return await fn();
38
36
  }
39
37
  finally {
40
- await flushPendingEmbeddings();
41
38
  closeDatabase();
42
39
  }
43
40
  }
@@ -243,27 +240,12 @@ program
243
240
  console.log(JSON.stringify(conflicts.length > 0 ? { entities, retrieval, conflicts } : { entities, retrieval }));
244
241
  }
245
242
  else if (entities.length === 0) {
246
- if (query && retrieval.degraded) {
247
- console.log('No results found. Semantic search is configured but could not run for this query (provider or index issue) — this was keyword-only. Run `memesh doctor` to check.');
248
- }
249
- else if (query && retrieval.mode === 'fts') {
250
- console.log('No results found. This was a keyword-only search — no semantic search is configured. See `memesh doctor` for Smart Mode.');
251
- }
252
- else {
253
- console.log('No results found.');
254
- }
243
+ console.log(query ? 'No results found in the keyword index.' : 'No results found.');
255
244
  }
256
245
  else {
257
- const allSemantic = query && entities.every((e) => e.match?.source === 'semantic');
258
- if (allSemantic) {
259
- console.log('No keyword matches. Closest memories by meaning — may be unrelated:');
260
- }
261
246
  for (const e of entities) {
262
247
  const badge = e.archived ? ' [archived]' : '';
263
- const semantic = e.match?.source === 'semantic'
264
- ? ` (~${Math.round((e.match.relevance ?? 0) * 100)}% semantic)`
265
- : '';
266
- console.log(` ${e.name}${badge} (${e.type})${semantic}`);
248
+ console.log(` ${e.name}${badge} (${e.type})`);
267
249
  for (const obs of e.observations.slice(0, 3)) {
268
250
  let shown = obs;
269
251
  if (obs.length > 500) {
@@ -280,9 +262,6 @@ program
280
262
  }
281
263
  const truncatedNote = retrieval.truncated ? ' (limit reached — more may exist)' : '';
282
264
  console.log(`\n${entities.length} result(s)${truncatedNote}`);
283
- if (retrieval.degraded) {
284
- console.log('Warning: semantic search unavailable right now — keyword-only results (degraded). Run `memesh doctor` to see why.');
285
- }
286
265
  if (conflicts.length > 0) {
287
266
  console.log('\nWarning: Conflicts detected:');
288
267
  for (const c of conflicts) {
@@ -344,54 +323,8 @@ function registerPinCommand(name, description, pinned, onFound) {
344
323
  });
345
324
  });
346
325
  }
347
- registerPinCommand('pin', 'Protect an entity from the dreamer’s auto-compaction', true, (e) => `📌 Pinned "${e}" — the dreamer will not compact it`);
348
- registerPinCommand('unpin', 'Allow the dreamer to auto-compact an entity again', false, (e) => `📍 Unpinned "${e}"`);
349
- program
350
- .command('consolidate')
351
- .description('(retired) Use `memesh dream` — see the message this prints')
352
- .allowUnknownOption()
353
- .action(() => {
354
- console.error('`memesh consolidate` has been retired.');
355
- console.error('');
356
- console.error('It rewrote a memory with an LLM summary and deleted the originals on the spot —');
357
- console.error('no proposal, no review, and nothing to restore from if the summary was wrong.');
358
- console.error('');
359
- console.error('`memesh dream` is the reviewed version of the same idea:');
360
- console.error(' memesh dream run propose digests for clusters of noisy memories');
361
- console.error(' memesh dream list see what it proposed');
362
- console.error(' memesh dream accept <id> / reject <id>');
363
- console.error('');
364
- console.error('Nothing is changed until you accept a proposal, and sources are archived');
365
- console.error('rather than deleted. It works on episodic memories (commits, session notes)');
366
- console.error('and never touches lessons, decisions, architecture notes, or pinned entities —');
367
- console.error('so it is not a like-for-like replacement for compressing one named entity.');
368
- process.exitCode = 1;
369
- });
370
- program
371
- .command('verify')
372
- .description('(retired) Removed with the agentic-orchestration experiment — see the message this prints')
373
- .allowUnknownOption()
374
- .action(() => {
375
- console.error('`memesh verify` has been retired, along with the agentic-orchestration experiment.');
376
- console.error('');
377
- console.error('It recorded a verification report for background-agent work. The protocol it');
378
- console.error('served was removed without ever leaving opt-in. Run your own verification');
379
- console.error('(typecheck / tests / lint) and store conclusions with `memesh remember` if you');
380
- console.error('want them remembered. Existing verification_record entities are untouched.');
381
- process.exitCode = 1;
382
- });
383
- program
384
- .command('patterns')
385
- .description('(retired) Removed with the agentic-orchestration experiment — see the message this prints')
386
- .allowUnknownOption()
387
- .action(() => {
388
- console.error('`memesh patterns` has been retired, along with the agentic-orchestration experiment.');
389
- console.error('');
390
- console.error('It displayed the experiment\'s local skill-usage telemetry, which is no longer');
391
- console.error('written. A leftover ~/.memesh/skill-usage.jsonl is inert and safe to delete.');
392
- console.error('For work-pattern insights, use the `user_patterns` MCP tool or GET /v1/patterns.');
393
- process.exitCode = 1;
394
- });
326
+ registerPinCommand('pin', 'Exclude an entity from digest work packages', true, (e) => `📌 Pinned "${e}" — excluded from digest work packages`);
327
+ registerPinCommand('unpin', 'Allow an entity in digest work packages again', false, (e) => `📍 Unpinned "${e}"`);
395
328
  program
396
329
  .command('export')
397
330
  .description('Export memories as JSON. Defaults to stdout (pipe-friendly); use `-o <file>` to write directly.')
@@ -824,7 +757,7 @@ agentCmd
824
757
  const routerTokenFile = path.join(messageDir, 'agent-router.token');
825
758
  ensureRouterTokenFile(routerTokenFile);
826
759
  const common = {
827
- router_socket: path.join(messageDir, 'agent-router.sock'),
760
+ router_socket: getAgentRouterSocketPath(),
828
761
  token_file: routerTokenFile,
829
762
  project: requireAgentScopeArg(opts.project, 'project', '--project'),
830
763
  principal_id: requireAgentScopeArg(opts.principal, 'recipient', '--principal'),
@@ -856,16 +789,19 @@ agentCmd
856
789
  ? 'ordinary-session-native-queue'
857
790
  : host === 'claude' ? 'session-owned-channel' : 'memesh-managed-session',
858
791
  session_identity: host === 'codex-session' ? 'codex-thread-id-at-session-start' : 'generated-per-process',
859
- ordinary_sessions: host === 'codex-session' ? 'explicit-workspace-opt-in' : 'presence-only/inbound-unavailable',
792
+ ordinary_sessions: host === 'codex-session' ? 'automatic-thread-scoped-with-workspace-override' : 'presence-only/inbound-unavailable',
860
793
  registration_command: registrationCommand,
861
794
  launch_command: launchCommand,
862
- next_command: registrationCommand ?? launchCommand ?? 'Restart Codex in the configured workspace',
795
+ next_command: registrationCommand ?? launchCommand ?? 'Restart Codex in the configured workspace to apply the identity override',
863
796
  };
864
797
  console.log(opts.json ? JSON.stringify(result) : [
865
798
  `Created owner-private ${host} config: ${configPath}`,
866
799
  'No active or stopped ordinary session was attached.',
867
800
  ...(registrationCommand ? [`Register once: ${registrationCommand}`] : []),
868
- ...(launchCommand ? [`Launch: ${launchCommand}`] : ['Restart Codex in the configured workspace.']),
801
+ ...(launchCommand ? [`Launch: ${launchCommand}`] : [
802
+ 'This optional override gives that workspace a stable named principal.',
803
+ 'Restart Codex in the configured workspace to apply it.',
804
+ ]),
869
805
  ].join('\n'));
870
806
  });
871
807
  program
@@ -1110,7 +1046,6 @@ configCmd
1110
1046
  .description('Show current configuration')
1111
1047
  .action(() => {
1112
1048
  const config = readConfig();
1113
- const caps = detectCapabilities(config);
1114
1049
  console.log('Configuration (~/.memesh/config.json):');
1115
1050
  const rows = buildConfigListing(config);
1116
1051
  if (rows.length === 0) {
@@ -1120,126 +1055,29 @@ configCmd
1120
1055
  for (const { key, value } of rows)
1121
1056
  console.log(` ${key}: ${value}`);
1122
1057
  }
1123
- console.log(`\nSearch level: ${caps.searchLevel} (${caps.searchLevel === 1 ? 'Smart Mode' : 'Core'})`);
1124
1058
  });
1125
- const SET_KEY_ALIASES = {
1126
- 'llm.api-key': 'llm.apiKey',
1127
- };
1128
- const ALLOWED_KEYS = new Set([
1129
- 'llm.provider',
1130
- 'llm.apiKey',
1131
- 'llm.model',
1132
- 'embedder.provider',
1133
- 'autoUpdate',
1134
- 'sessionLimit',
1135
- 'autoCapture',
1136
- 'llmFallbacks',
1137
- 'language',
1138
- 'transcriptMining',
1139
- ]);
1059
+ const ALLOWED_KEYS = new Set(['autoUpdate', 'sessionLimit', 'autoCapture']);
1140
1060
  const KEY_VALIDATORS = {
1141
- 'llm.provider': (v) => ['anthropic', 'openai', 'ollama'].includes(v) ? null : `must be one of: anthropic, openai, ollama`,
1142
- 'language': (v) => {
1143
- if (v.trim().length === 0)
1144
- return 'must not be blank — use `memesh config unset language` to clear it';
1145
- if (v.length > MAX_LANGUAGE_LENGTH)
1146
- return `must be ${MAX_LANGUAGE_LENGTH} characters or fewer (a language name or locale code)`;
1147
- return languageValueError(v);
1148
- },
1149
- 'embedder.provider': (v) => ['openai', 'ollama'].includes(v) ? null : `must be one of: openai, ollama`,
1150
- 'autoUpdate': (v) => ['off', 'patch', 'minor', 'major'].includes(v) ? null : `must be one of: off, patch, minor, major`,
1151
- 'autoCapture': (v) => ['true', 'false', '1', '0'].includes(v) ? null : `must be one of: true, false, 1, 0`,
1152
- 'transcriptMining': (v) => ['true', 'false', '1', '0'].includes(v) ? null : `must be one of: true, false, 1, 0`,
1153
- 'llmFallbacks': (v) => {
1154
- let parsed;
1155
- try {
1156
- parsed = JSON.parse(v);
1157
- }
1158
- catch {
1159
- return 'must be a JSON array, e.g. \'[{"provider":"openai","model":"gpt-4o-mini","apiKey":"sk-..."}]\'';
1160
- }
1161
- if (!Array.isArray(parsed))
1162
- return 'must be a JSON ARRAY of provider objects';
1163
- for (const entry of parsed) {
1164
- if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
1165
- return 'every entry must be an object like {"provider":"openai"}';
1166
- }
1167
- const provider = entry.provider;
1168
- if (!['anthropic', 'openai', 'ollama'].includes(String(provider))) {
1169
- return `every entry needs provider = anthropic | openai | ollama (got ${JSON.stringify(provider)})`;
1170
- }
1171
- }
1172
- return null;
1173
- },
1061
+ autoUpdate: (v) => ['off', 'patch', 'minor', 'major'].includes(v) ? null : 'must be one of: off, patch, minor, major',
1062
+ autoCapture: (v) => ['true', 'false', '1', '0'].includes(v) ? null : 'must be one of: true, false, 1, 0',
1174
1063
  };
1175
- function setNested(obj, path, value) {
1176
- let cur = obj;
1177
- for (let i = 0; i < path.length - 1; i++) {
1178
- const part = path[i];
1179
- if (typeof cur[part] !== 'object' || cur[part] === null) {
1180
- cur[part] = {};
1181
- }
1182
- cur = cur[part];
1183
- }
1184
- cur[path[path.length - 1]] = value;
1185
- }
1186
- function getNested(obj, path) {
1187
- return path.reduce((cur, part) => (cur && typeof cur === 'object' ? cur[part] : undefined), obj);
1188
- }
1189
- const REDACTED = '***';
1190
- function formatConfigValue(key, raw) {
1191
- if (key.toLowerCase().includes('key'))
1192
- return REDACTED;
1193
- if (key === 'llmFallbacks' && Array.isArray(raw)) {
1194
- const redacted = raw.map((fb) => {
1195
- if (fb && typeof fb === 'object') {
1196
- const { apiKey, ...rest } = fb;
1197
- return apiKey ? { ...rest, apiKey: REDACTED } : rest;
1198
- }
1199
- return fb;
1200
- });
1201
- return JSON.stringify(redacted);
1202
- }
1203
- if (typeof raw === 'object')
1204
- return JSON.stringify(raw);
1205
- return String(raw);
1206
- }
1207
1064
  function buildConfigListing(config) {
1208
1065
  const rows = [];
1209
1066
  for (const key of Array.from(ALLOWED_KEYS).sort()) {
1210
- const raw = getNested(config, key.split('.'));
1067
+ const raw = config[key];
1211
1068
  if (raw === undefined || raw === null)
1212
1069
  continue;
1213
- rows.push({ key, value: formatConfigValue(key, raw) });
1070
+ rows.push({ key, value: String(raw) });
1214
1071
  }
1215
1072
  return rows;
1216
1073
  }
1217
- function deleteNested(obj, path) {
1218
- if (path.length === 0)
1219
- return false;
1220
- if (path.length === 1) {
1221
- if (path[0] in obj) {
1222
- delete obj[path[0]];
1223
- return true;
1224
- }
1225
- return false;
1226
- }
1227
- const head = path[0];
1228
- if (typeof obj[head] !== 'object' || obj[head] === null)
1229
- return false;
1230
- const child = obj[head];
1231
- const removed = deleteNested(child, path.slice(1));
1232
- if (removed && Object.keys(child).length === 0)
1233
- delete obj[head];
1234
- return removed;
1235
- }
1236
1074
  configCmd
1237
1075
  .command('set')
1238
- .description('Set a config value (e.g. llm.provider, embedder.provider)')
1076
+ .description('Set an ordinary config value (autoCapture, sessionLimit, autoUpdate)')
1239
1077
  .argument('<key>', 'Config key — see `memesh config list` for valid keys')
1240
1078
  .argument('<value>', 'Config value')
1241
1079
  .action((key, value) => {
1242
- const canonical = SET_KEY_ALIASES[key] ?? key;
1080
+ const canonical = key;
1243
1081
  if (!ALLOWED_KEYS.has(canonical)) {
1244
1082
  console.error(`Unknown key: ${key}`);
1245
1083
  console.error(`Allowed keys: ${Array.from(ALLOWED_KEYS).sort().join(', ')}`);
@@ -1257,42 +1095,30 @@ configCmd
1257
1095
  if (canonical === 'sessionLimit') {
1258
1096
  coerced = wholeNumber('sessionLimit')(value);
1259
1097
  }
1260
- if (canonical === 'llmFallbacks')
1261
- coerced = JSON.parse(value);
1262
1098
  if (canonical === 'autoCapture') {
1263
1099
  coerced = value === 'true' || value === '1';
1264
1100
  }
1265
- if (canonical === 'transcriptMining') {
1266
- coerced = value === 'true' || value === '1';
1267
- }
1268
- const config = readConfig();
1269
- setNested(config, canonical.split('.'), coerced);
1270
- writeConfig(config);
1271
- const displayValue = canonical.toLowerCase().includes('key') ? maskApiKey(String(value)) : String(value);
1101
+ updateConfig({ [canonical]: coerced });
1102
+ const displayValue = String(value);
1272
1103
  console.log(`✅ Set ${canonical} = ${displayValue}`);
1273
- if (canonical === 'llm.apiKey' && !config.llm?.provider) {
1274
- console.log('⚠️ No llm.provider is set, so this key is not used yet and LLM features stay off.');
1275
- console.log(' Set one with: memesh config set llm.provider <anthropic|openai|ollama>');
1276
- }
1277
1104
  });
1278
1105
  configCmd
1279
1106
  .command('unset')
1280
- .description('Remove a config value (supports nested keys like llm.apiKey)')
1107
+ .description('Remove a config value (ordinary settings only)')
1281
1108
  .argument('<key>', 'Config key — see `memesh config list` for valid keys')
1282
1109
  .action((key) => {
1283
- const canonical = SET_KEY_ALIASES[key] ?? key;
1110
+ const canonical = key;
1284
1111
  if (!ALLOWED_KEYS.has(canonical)) {
1285
1112
  console.error(`Unknown key: ${key}`);
1286
1113
  console.error(`Allowed keys: ${Array.from(ALLOWED_KEYS).sort().join(', ')}`);
1287
1114
  process.exit(1);
1288
1115
  }
1289
- const config = readConfig();
1290
- const removed = deleteNested(config, canonical.split('.'));
1116
+ const removed = canonical in readConfig();
1117
+ updateConfig({ [canonical]: undefined });
1291
1118
  if (!removed) {
1292
1119
  console.log(`(no change — ${canonical} was not set)`);
1293
1120
  return;
1294
1121
  }
1295
- writeConfig(config);
1296
1122
  console.log(`✅ Removed ${canonical}`);
1297
1123
  });
1298
1124
  program
@@ -1450,77 +1276,12 @@ program
1450
1276
  }
1451
1277
  process.exit(run.status ?? 1);
1452
1278
  });
1453
- program
1454
- .command('telemetry')
1455
- .description('Show LLM call telemetry (per-flow scorecard for the last N days)')
1456
- .option('--window <days>', 'Look-back window in days (default 30)', wholeNumber('--window'), 30)
1457
- .option('--prune <days>', 'Delete rows older than N days BEFORE rendering (closes v4.2.0 retention gap)', wholeNumber('--prune', 0))
1458
- .option('--json', 'Output as JSON')
1459
- .action(async (opts) => {
1460
- await withDatabase(async () => {
1461
- const { summariseTelemetry, pruneTelemetry } = await import('../../core/llm-telemetry.js');
1462
- let pruneResult = null;
1463
- if (typeof opts.prune === 'number' && Number.isFinite(opts.prune) && opts.prune >= 0) {
1464
- pruneResult = pruneTelemetry({ olderThanDays: opts.prune });
1465
- }
1466
- const summaries = summariseTelemetry(opts.window);
1467
- if (opts.json) {
1468
- console.log(JSON.stringify({ pruned: pruneResult, summaries }, null, 2));
1469
- return;
1470
- }
1471
- if (pruneResult) {
1472
- console.log(`Pruned ${pruneResult.deletedRows} row${pruneResult.deletedRows === 1 ? '' : 's'} older than ${opts.prune} days.`);
1473
- console.log('');
1474
- }
1475
- if (summaries.length === 0) {
1476
- console.log(`No LLM telemetry recorded in the last ${opts.window} days.`);
1477
- console.log(`(Smart-Mode flows write rows automatically — run \`memesh dream run\`, \`memesh dream patterns\`, or trigger a session with errors to populate.)`);
1478
- return;
1479
- }
1480
- console.log(`LLM telemetry — last ${opts.window} days`);
1481
- console.log('');
1482
- for (const s of summaries) {
1483
- const successRate = s.total_attempts > 0 ? Math.round((s.successes / s.total_attempts) * 100) : 0;
1484
- console.log(`▸ ${s.flow}`);
1485
- console.log(` calls: ${s.total_calls} (${s.total_attempts} provider attempts)`);
1486
- console.log(` success rate: ${successRate}% (${s.successes} ok, ${s.failures} failed)`);
1487
- if (s.fallback_used > 0) {
1488
- console.log(` fallback used: ${s.fallback_used} time${s.fallback_used === 1 ? '' : 's'} ⚠️ primary failed`);
1489
- }
1490
- if (s.median_latency_ms != null) {
1491
- console.log(` median latency: ${s.median_latency_ms}ms`);
1492
- }
1493
- const okFail = (rec) => Object.entries(rec).map(([k, v]) => `${k}=${v.ok}/${v.ok + v.fail}`).join(', ');
1494
- const providers = okFail(s.by_provider);
1495
- if (providers)
1496
- console.log(` by provider: ${providers}`);
1497
- const models = okFail(s.by_model);
1498
- if (models)
1499
- console.log(` by model: ${models}`);
1500
- const projects = okFail(s.by_project);
1501
- if (projects)
1502
- console.log(` by project: ${projects}`);
1503
- const errors = Object.entries(s.by_error_class);
1504
- if (errors.length > 0) {
1505
- const errStr = errors.sort((a, b) => b[1] - a[1]).map(([c, n]) => `${c}=${n}`).join(', ');
1506
- console.log(` error classes: ${errStr}`);
1507
- }
1508
- if (s.sample_errors.length > 0) {
1509
- console.log(` recent errors:`);
1510
- for (const e of s.sample_errors) {
1511
- console.log(` • [${e.error_class ?? 'unknown'}] ${e.message.slice(0, 100)}`);
1512
- }
1513
- }
1514
- console.log('');
1515
- }
1516
- });
1517
- });
1518
1279
  const kgCmd = program
1519
1280
  .command('kg')
1520
1281
  .description('Knowledge graph maintenance');
1521
1282
  kgCmd
1522
1283
  .command('backfill-relations')
1523
- .description('Propose / apply heuristic relations to connect orphan entities (no LLM)')
1284
+ .description('Propose / apply deterministic relations to connect orphan entities')
1524
1285
  .option('--project <name>', 'Restrict to one project')
1525
1286
  .option('--dry-run', 'Show proposals without writing (default off — use to preview)')
1526
1287
  .option('--max-per-source <n>', 'Max edges per orphan (default 3)', wholeNumber('--max-per-source'), 3)
@@ -1682,7 +1443,6 @@ program
1682
1443
  .description('Verify local install health and show actionable fixes')
1683
1444
  .option('--json', 'Output machine-readable diagnostics as JSON')
1684
1445
  .option('--probe-http', 'Also probe the local HTTP server health endpoint')
1685
- .option('--probe', 'Make one small live call to the configured LLM to confirm it actually answers')
1686
1446
  .option('--url <url>', 'Base URL for --probe-http', 'http://127.0.0.1:3737')
1687
1447
  .option('--fix', 'Apply the whitelisted fixes doctor prescribes (asks per fix; --yes skips asking)')
1688
1448
  .option('--yes', 'With --fix: apply without asking')
@@ -1692,7 +1452,6 @@ program
1692
1452
  packageRoot,
1693
1453
  packageVersion: pkg.version,
1694
1454
  probeHttp: opts.probeHttp,
1695
- probeCapabilities: opts.probe,
1696
1455
  httpBaseUrl: opts.url,
1697
1456
  });
1698
1457
  if (opts.fix) {
@@ -1750,7 +1509,6 @@ program
1750
1509
  packageRoot,
1751
1510
  packageVersion: pkg.version,
1752
1511
  probeHttp: false,
1753
- probeCapabilities: false,
1754
1512
  httpBaseUrl: opts.url,
1755
1513
  });
1756
1514
  for (const c of result.checks) {
@@ -1776,207 +1534,7 @@ program
1776
1534
  process.exitCode = 1;
1777
1535
  }
1778
1536
  });
1779
- const dreamCmd = program.command('dream').description('Consolidate noisy episodic memories into digests (LLM-driven, opt-in review)');
1780
- dreamCmd
1781
- .command('run', { isDefault: true })
1782
- .description('Run a dream pass — propose digests for clusters of compactable entities')
1783
- .option('--project <name>', 'Restrict to one project')
1784
- .option('--dry-run', 'Compute proposals without writing to dream_proposals')
1785
- .option('--max-llm-calls <n>', 'Hard cap on LLM calls (default 100)', wholeNumber('--max-llm-calls'))
1786
- .option('--window-days <n>', 'Look-back window in days (default 56 = 8 weeks)', wholeNumber('--window-days'))
1787
- .option('--validate', 'Run a second LLM pass to cross-check each digest against its sources (doubles LLM calls per proposal; surfaces under flow=digest_validator in `memesh telemetry`)')
1788
- .option('--from-transcripts', 'EXPERIMENTAL: mine Claude Code session transcripts for this project (decisions/lessons/facts hidden in the conversation) and STAGE them as proposals for `dream accept`, instead of clustering existing entities. Scoped to the current project only — --project does not apply here. With --dry-run, lists sessions and conversation-turn counts without calling an LLM.')
1789
- .option('--if-due', 'For a scheduler (cron/launchd): only mine if `transcriptMining` is enabled in config AND at least --min-interval-hours have passed since this project was last mined; otherwise exit 0 doing nothing. Lets one frequently-firing entry self-throttle. Only meaningful with --from-transcripts.')
1790
- .option('--min-interval-hours <n>', 'With --if-due: minimum hours between mined runs for this project (default 24).', wholeNumber('--min-interval-hours'))
1791
- .action(async (opts) => {
1792
- if (opts.fromTranscripts) {
1793
- const windowDays = typeof opts.windowDays === 'number' && !Number.isNaN(opts.windowDays) ? opts.windowDays : 3;
1794
- if (opts.dryRun) {
1795
- const { scanTranscripts } = await import('../../core/transcript-source.js');
1796
- const { countConversationTurns } = await import('../../core/transcript-extractor.js');
1797
- const sessions = scanTranscripts({ windowDays });
1798
- console.log(`[dry-run] transcript sessions for this project in the last ${windowDays} day(s): ${sessions.length}`);
1799
- let totalTurns = 0;
1800
- for (const s of sessions) {
1801
- const turns = countConversationTurns(s.path);
1802
- totalTurns += turns;
1803
- console.log(` ${s.sessionId} ${turns} conversation turns ${s.lineCount} lines ${(s.sizeBytes / 1024).toFixed(0)}KB ${s.modifiedAt}`);
1804
- }
1805
- console.log(` total: ${totalTurns} conversation turns across ${sessions.length} session(s)`);
1806
- console.log('');
1807
- console.log('Run without --dry-run to extract high-value memories and stage them as proposals.');
1808
- return;
1809
- }
1810
- if (opts.ifDue) {
1811
- const { isTranscriptMiningEnabled } = await import('../../core/config.js');
1812
- if (!isTranscriptMiningEnabled(readConfig())) {
1813
- console.log('Scheduled transcript mining is off (opt-in). Enable it with `memesh config set transcriptMining true`; this scheduled run will then start mining when due.');
1814
- return;
1815
- }
1816
- const { getProjectName } = await import('../../core/paths.js');
1817
- const { lastTranscriptMineAt, transcriptMiningDue } = await import('../../core/transcript-source.js');
1818
- const projectKey = getProjectName(process.cwd());
1819
- const intervalH = typeof opts.minIntervalHours === 'number' && !Number.isNaN(opts.minIntervalHours) ? opts.minIntervalHours : 24;
1820
- const last = lastTranscriptMineAt(projectKey);
1821
- if (!transcriptMiningDue(Date.now(), last, intervalH)) {
1822
- const agoH = last === null ? null : (Date.now() - last) / 3600_000;
1823
- console.log(`Not due yet: this project was mined ${agoH === null ? 'recently' : `${agoH.toFixed(1)}h ago`}; interval is ${intervalH}h. Nothing to do.`);
1824
- return;
1825
- }
1826
- }
1827
- if (opts.project) {
1828
- console.log('note: --project does not scope --from-transcripts — the transcript source is always the current project. Ignoring --project.');
1829
- }
1830
- await withDatabase(async () => {
1831
- const { runTranscriptSource } = await import('../../core/transcript-extractor.js');
1832
- const { getDatabase } = await import('../../db.js');
1833
- const cfg = readConfig();
1834
- const llm = detectCapabilities().llm;
1835
- if (!llm) {
1836
- console.error('No LLM configured. Run `memesh config set llm.provider <anthropic|openai|ollama>` first (or set ANTHROPIC_API_KEY / OPENAI_API_KEY).');
1837
- console.error('LLM is required for `--from-transcripts` because extracting durable memory from prose is a semantic decision, not a rule.');
1838
- process.exit(1);
1839
- }
1840
- const result = await runTranscriptSource(getDatabase(), llm, {
1841
- windowDays,
1842
- maxLlmCalls: opts.maxLlmCalls,
1843
- fallbacks: cfg.llmFallbacks,
1844
- });
1845
- const { getProjectName } = await import('../../core/paths.js');
1846
- const { recordTranscriptMine } = await import('../../core/transcript-source.js');
1847
- recordTranscriptMine(getProjectName(process.cwd()), Date.now());
1848
- console.log(`Transcript mining complete in ${result.durationMs}ms`);
1849
- console.log(` sessions scanned: ${result.sessionsScanned}`);
1850
- console.log(` LLM calls: ${result.llmCalls}`);
1851
- console.log(` candidates extracted: ${result.candidatesExtracted}`);
1852
- console.log(` proposals created: ${result.proposalsCreated}`);
1853
- if (result.duplicatesSkipped > 0)
1854
- console.log(` duplicates skipped: ${result.duplicatesSkipped} (already a pending proposal)`);
1855
- if (result.nearDuplicatesSkipped > 0) {
1856
- console.log(` near-duplicates skipped: ${result.nearDuplicatesSkipped} candidate(s) skipped as near-duplicates of existing memories`);
1857
- for (const d of result.nearDuplicates) {
1858
- console.log(` - "${d.candidateName}" ~= existing "${d.matchedEntityName}" (distance ${d.distance.toFixed(3)})`);
1859
- }
1860
- }
1861
- if (result.secretsDropped > 0)
1862
- console.log(` secret-bearing candidates dropped: ${result.secretsDropped}`);
1863
- if (result.llmFailures > 0)
1864
- console.log(` LLM call failures: ${result.llmFailures} (sessions not mined — retry when the provider is reachable)`);
1865
- if (result.parseFailures > 0)
1866
- console.log(` unparsable replies: ${result.parseFailures} (chunk reply not valid JSON — likely truncated; those candidates were lost, retry)`);
1867
- if (result.cappedTurns > 0) {
1868
- console.log(` per-turn cap: ${result.cappedTurns} turn(s) were analysed only in part `
1869
- + `(the first 4,000 characters); the rest of each was not sent`);
1870
- }
1871
- if (result.truncatedTurns > 0) {
1872
- console.log(` size-cap truncation: ${result.truncatedTurns} conversation turn(s) beyond the cap were NOT analysed`);
1873
- for (const t of result.truncatedSessions) {
1874
- console.log(` - session ${t.sessionId}: ${t.truncatedTurns} tail turn(s) not analysed`);
1875
- }
1876
- }
1877
- if (result.skipped.length > 0) {
1878
- const reasonCounts = new Map();
1879
- for (const s of result.skipped)
1880
- reasonCounts.set(s.reason, (reasonCounts.get(s.reason) ?? 0) + 1);
1881
- console.log(` skipped: ${result.skipped.length}`);
1882
- for (const [reason, n] of reasonCounts)
1883
- console.log(` - ${reason}${n > 1 ? ` (×${n})` : ''}`);
1884
- }
1885
- if (result.proposalsCreated > 0) {
1886
- console.log('');
1887
- console.log('Review with: memesh dream list');
1888
- console.log('Accept: memesh dream accept <id>');
1889
- }
1890
- });
1891
- return;
1892
- }
1893
- await withDatabase(async () => {
1894
- const { runDreamer } = await import('../../core/dreamer.js');
1895
- const { getDatabase } = await import('../../db.js');
1896
- const cfg = readConfig();
1897
- const llm = detectCapabilities().llm;
1898
- if (!llm) {
1899
- console.error('No LLM configured. Run `memesh config set llm.provider <anthropic|openai|ollama>` first (or set ANTHROPIC_API_KEY / OPENAI_API_KEY).');
1900
- console.error('LLM is required for `memesh dream` because consolidation is a semantic decision, not a rule.');
1901
- process.exit(1);
1902
- }
1903
- const result = await runDreamer(getDatabase(), llm, {
1904
- project: opts.project,
1905
- dryRun: !!opts.dryRun,
1906
- maxLlmCalls: opts.maxLlmCalls,
1907
- windowDays: opts.windowDays,
1908
- fallbacks: cfg.llmFallbacks,
1909
- validateBeforeStage: !!opts.validate,
1910
- });
1911
- console.log(`${opts.dryRun ? '[dry-run] ' : ''}Dream pass complete in ${result.durationMs}ms`);
1912
- console.log(` clusters scanned: ${result.clustersScanned}`);
1913
- if (result.clusteringMode) {
1914
- console.log(` grouped by: ${result.clusteringMode === 'semantic' ? 'meaning (embeddings)' : 'calendar week (no embeddings)'}`);
1915
- }
1916
- if (result.clusteringNote)
1917
- console.log(` note: ${result.clusteringNote}`);
1918
- console.log(` LLM calls: ${result.llmCalls}`);
1919
- console.log(` proposals created: ${result.proposalsCreated}`);
1920
- if (result.skipped.length > 0) {
1921
- console.log(` skipped: ${result.skipped.length}`);
1922
- const reasonCounts = new Map();
1923
- for (const s of result.skipped) {
1924
- reasonCounts.set(s.reason, (reasonCounts.get(s.reason) ?? 0) + 1);
1925
- }
1926
- for (const [reason, n] of reasonCounts) {
1927
- console.log(` - ${reason}${n > 1 ? ` (×${n})` : ''}`);
1928
- }
1929
- }
1930
- if (!opts.dryRun && result.proposalsCreated > 0) {
1931
- console.log('');
1932
- console.log(`Review with: memesh dream list`);
1933
- console.log(`Accept all: memesh dream accept --all`);
1934
- }
1935
- });
1936
- });
1937
- dreamCmd
1938
- .command('patterns')
1939
- .description('Run pattern detector — surface emerging patterns/conventions/repeated mistakes per project (Phase 3)')
1940
- .option('--project <name>', 'Restrict to one project (default: all projects)')
1941
- .option('--dry-run', 'Compute proposals without writing to dream_proposals')
1942
- .option('--max-llm-calls <n>', 'Hard cap on LLM calls (default 10)', wholeNumber('--max-llm-calls'))
1943
- .option('--window-days <n>', 'Look-back window in days (default 30)', wholeNumber('--window-days'))
1944
- .option('--min-signal <n>', 'Minimum signal_score to include in scan (default 0.3)', unitFraction('--min-signal'))
1945
- .action(async (opts) => {
1946
- await withDatabase(async () => {
1947
- const { runPatternDetector } = await import('../../core/dreamer.js');
1948
- const { getDatabase } = await import('../../db.js');
1949
- const cfg = readConfig();
1950
- const llm = detectCapabilities().llm;
1951
- if (!llm) {
1952
- console.error('No LLM configured. Pattern detection requires an LLM.');
1953
- console.error('Run `memesh config set llm.provider <anthropic|openai|ollama>` first (or set ANTHROPIC_API_KEY / OPENAI_API_KEY).');
1954
- process.exit(1);
1955
- }
1956
- const result = await runPatternDetector(getDatabase(), llm, {
1957
- project: opts.project,
1958
- dryRun: !!opts.dryRun,
1959
- maxLlmCalls: opts.maxLlmCalls,
1960
- windowDays: opts.windowDays,
1961
- minSignal: opts.minSignal,
1962
- fallbacks: cfg.llmFallbacks,
1963
- });
1964
- console.log(`${opts.dryRun ? '[dry-run] ' : ''}Pattern detector complete in ${result.durationMs}ms`);
1965
- console.log(` entities scanned: ${result.entitiesScanned}`);
1966
- console.log(` LLM calls: ${result.llmCalls}`);
1967
- console.log(` patterns proposed: ${result.proposalsCreated}`);
1968
- if (result.skipped.length > 0) {
1969
- console.log(` skipped: ${result.skipped.length}`);
1970
- for (const s of result.skipped.slice(0, 5)) {
1971
- console.log(` - ${s.project ?? '?'}: ${s.reason}`);
1972
- }
1973
- }
1974
- if (!opts.dryRun && result.proposalsCreated > 0) {
1975
- console.log('');
1976
- console.log(`Review with: memesh dream list`);
1977
- }
1978
- });
1979
- });
1537
+ const dreamCmd = program.command('dream').description('Review agent-submitted proposals: list, show, accept, or reject');
1980
1538
  dreamCmd
1981
1539
  .command('list')
1982
1540
  .description('List dream proposals (pending by default)')
@@ -2011,49 +1569,6 @@ dreamCmd
2011
1569
  console.log(`Inspect: memesh dream show <id> | Apply: memesh dream accept <id> | Reject: memesh dream reject <id>`);
2012
1570
  });
2013
1571
  });
2014
- dreamCmd
2015
- .command('conflicts')
2016
- .description('Judge semantically-close memory pairs for contradiction / supersession / duplication (LLM) and stage relation proposals for review')
2017
- .option('--max-pairs <n>', 'Judge at most N of the tightest candidate pairs this run (default 20)', wholeNumber('--max-pairs'))
2018
- .option('--dry-run', 'Show how many candidates are queued without calling an LLM or writing anything')
2019
- .action(async (opts) => {
2020
- await withDatabase(async () => {
2021
- const cfg = readConfig();
2022
- const llm = detectCapabilities().llm;
2023
- if (!llm) {
2024
- console.error('No LLM configured. Run `memesh config set llm.provider <anthropic|openai|ollama>` first (or set ANTHROPIC_API_KEY / OPENAI_API_KEY).');
2025
- console.error('LLM is required for `memesh dream conflicts` because "do these two entries disagree" is a semantic judgement, not a rule.');
2026
- process.exit(1);
2027
- }
2028
- const { judgeConflicts, CONFLICT_JUDGE_MAX_PAIRS } = await import('../../core/conflict-judge.js');
2029
- const { getDatabase } = await import('../../db.js');
2030
- const result = await judgeConflicts(getDatabase(), llm, {
2031
- maxPairs: typeof opts.maxPairs === 'number' && !Number.isNaN(opts.maxPairs) ? opts.maxPairs : CONFLICT_JUDGE_MAX_PAIRS,
2032
- dryRun: !!opts.dryRun,
2033
- fallbacks: cfg.llmFallbacks,
2034
- });
2035
- console.log(`${opts.dryRun ? '[dry-run] ' : ''}Conflict pass complete in ${result.durationMs}ms`);
2036
- console.log(` candidate pairs available: ${result.candidatesAvailable}`);
2037
- if (opts.dryRun) {
2038
- console.log(' (dry run — no LLM called, nothing judged or written)');
2039
- return;
2040
- }
2041
- console.log(` LLM calls: ${result.llmCalls}`);
2042
- console.log(` judged: ${result.judged} (${result.unrelated} unrelated, remembered so they are never re-bought)`);
2043
- console.log(` staged: ${result.staged} relation proposal(s)`);
2044
- if (result.llmFailures > 0) {
2045
- console.log(` failed: ${result.llmFailures} (unparseable or errored LLM responses; those pairs stay at the head of the candidate list and are retried next run)`);
2046
- }
2047
- if (result.aborted) {
2048
- console.error(` ABORTED after the work above: ${result.aborted}`);
2049
- process.exit(1);
2050
- }
2051
- if (result.staged > 0) {
2052
- console.log('');
2053
- console.log('Review: memesh dream list | Apply: memesh dream accept <id> | Reject: memesh dream reject <id>');
2054
- }
2055
- });
2056
- });
2057
1572
  dreamCmd
2058
1573
  .command('show <id>')
2059
1574
  .description('Show a proposal in full — name, type, ALL observations, tags, source — so you can review the whole thing before accepting')
@@ -2315,184 +1830,47 @@ program
2315
1830
  const { command, args } = feedbackBrowserOpenCommand(process.platform, url);
2316
1831
  try {
2317
1832
  const child = spawn(command, args, { stdio: 'ignore', detached: true });
2318
- child.unref();
2319
- console.log(`Opened browser to file ${fbType} issue.`);
2320
- console.log('Edit the title + body before submitting.');
1833
+ const opened = await new Promise((resolve) => {
1834
+ child.once('spawn', () => resolve(true));
1835
+ child.once('error', () => resolve(false));
1836
+ });
1837
+ if (opened) {
1838
+ child.unref();
1839
+ console.log(`Opened browser to file ${fbType} issue.`);
1840
+ console.log('Edit the title + body before submitting.');
1841
+ }
1842
+ else {
1843
+ console.log('Could not open browser. URL:');
1844
+ console.log(url);
1845
+ }
2321
1846
  }
2322
1847
  catch {
2323
1848
  console.log('Could not open browser. URL:');
2324
1849
  console.log(url);
2325
1850
  }
2326
1851
  });
2327
- program
2328
- .command('reindex')
2329
- .description('Regenerate vector embeddings for all entities (--fts rebuilds the keyword index instead)')
2330
- .option('--namespace <namespace>', 'Reindex only entities in this namespace')
2331
- .option('--fts', 'Rebuild the full-text keyword index instead of the vector index')
2332
- .option('--discard-generation', 'Throw away a half-built vector index left by an interrupted rebuild, without rebuilding')
1852
+ program.command('reindex')
1853
+ .description('Rebuild the full-text keyword index')
1854
+ .requiredOption('--fts', 'Rebuild the full-text keyword index')
2333
1855
  .option('--json', 'Output as JSON')
2334
- .addOption(new Option('--vectors').hideHelp())
2335
1856
  .action(async (opts) => {
2336
- if (opts.vectors) {
2337
- if (opts.json) {
2338
- console.log(JSON.stringify({
2339
- refused: true,
2340
- reason: 'the --vectors flag is retired',
2341
- fix: 'run plain memesh reindex; to change provider first: memesh config set embedder.provider ollama|openai',
2342
- indexTouched: false,
2343
- }));
2344
- process.exit(1);
2345
- }
2346
- console.error('`memesh reindex --vectors` has been retired.');
2347
- console.error('');
2348
- console.error('It existed to consent to dropping every stored embedding before a refill.');
2349
- console.error('A rebuild now happens beside the live index and replaces it only when');
2350
- console.error('complete, so there is nothing left to consent to.');
2351
- console.error('');
2352
- console.error('Run plain `memesh reindex`. To change embedding provider first:');
2353
- console.error(' memesh config set embedder.provider ollama (or openai)');
2354
- process.exit(1);
2355
- }
2356
- requireOneOf(opts.namespace, NAMESPACES, '--namespace');
2357
- if (opts.discardGeneration) {
2358
- await withDatabase(async () => {
2359
- const read = readVectorGeneration();
2360
- const staged = generationRowIds().size;
2361
- if (read.state === 'none' && staged === 0) {
2362
- if (opts.json)
2363
- console.log(JSON.stringify({ discarded: false, staged: 0 }));
2364
- else
2365
- console.log('Nothing to discard: there is no half-built vector index.');
2366
- return;
2367
- }
2368
- const describe = read.state === 'open'
2369
- ? `${read.info.dimension}-dim, provider ${read.info.provider}, started ${read.info.startedAt}`
2370
- : read.state === 'unreadable'
2371
- ? `marker unreadable (${read.detail})`
2372
- : 'no marker';
2373
- discardVectorGeneration();
2374
- if (opts.json) {
2375
- console.log(JSON.stringify({ discarded: true, staged, generation: read, liveIndexTouched: false }));
2376
- }
2377
- else {
2378
- console.log(`Discarded a half-built vector index: ${staged} staged vectors (${describe}).\n` +
2379
- ' Your live index was not touched. Run `memesh reindex` to build a new one.');
2380
- }
2381
- });
2382
- return;
2383
- }
2384
- try {
2385
- if (opts.fts) {
2386
- await withDatabase(async () => {
2387
- const { entities } = reindexFts();
2388
- if (opts.json) {
2389
- console.log(JSON.stringify({ rebuilt: 'fts', entities }));
2390
- }
2391
- else {
2392
- console.log(`✅ Keyword index rebuilt from ${entities} active memories.`);
2393
- }
2394
- });
2395
- return;
2396
- }
2397
- if (!opts.namespace && !(await canRefillVectorIndex())) {
2398
- const embedder = detectCapabilities().embeddings;
2399
- const written = readConfig().embedder?.provider;
2400
- const known = embedder === 'openai' || embedder === 'ollama';
2401
- const invalid = written !== undefined && !known;
2402
- const unconfigured = written === undefined && !known;
2403
- const reason = invalid
2404
- ? `embedder.provider is set to '${written}', which is not a provider MeMesh knows`
2405
- : unconfigured
2406
- ? 'no embedding provider is configured, so there is nothing to build vectors with'
2407
- : 'could not produce a test embedding at the configured vector width';
2408
- const fix = invalid || unconfigured
2409
- ? (invalid ? 'Set it to one MeMesh knows: ' : 'Pick one first: ') +
2410
- '`memesh config set embedder.provider ollama` (local, needs `ollama serve`) ' +
2411
- 'or `memesh config set embedder.provider openai` (needs OPENAI_API_KEY). Until then, recall ' +
2412
- 'runs on keyword search, which needs no rebuild.'
2413
- : 'Check that Ollama is running (or that your OpenAI API key is valid), then run this again. ' +
2414
- '`memesh doctor` reports which provider is configured.';
2415
- if (opts.json) {
2416
- console.log(JSON.stringify({ refused: true, reason, fix, indexTouched: false }));
2417
- }
2418
- else {
2419
- console.error(`❌ Nothing was rebuilt: ${reason}.\n` +
2420
- ' Your existing index is untouched and still answering queries.\n' +
2421
- ` ${fix}`);
2422
- }
2423
- process.exit(1);
2424
- }
2425
- await withDatabase(async () => {
2426
- const result = await reindex({ namespace: opts.namespace });
2427
- const incomplete = result.missingVectors > 0
2428
- || result.failed > 0
2429
- || result.generationSwapped === false
2430
- || result.abortedAfter !== null;
2431
- if (opts.json) {
2432
- console.log(JSON.stringify(result));
2433
- }
2434
- else {
2435
- console.log(incomplete ? `⚠️ Reindex incomplete:` : `✅ Reindex complete:`);
2436
- console.log(` Processed: ${result.processed}`);
2437
- console.log(` Embedded: ${result.embedded}`);
2438
- console.log(` Skipped: ${result.skipped}`);
2439
- if (incomplete) {
2440
- if (result.abortedAfter !== null) {
2441
- console.log(` Stopped early after ${result.abortedAfter} entities: the provider failed ` +
2442
- `repeatedly. Everything embedded so far is kept — run this again to continue.`);
2443
- }
2444
- if (result.generationSwapped === false) {
2445
- console.log(` The new index was NOT switched in, so your existing index is untouched ` +
2446
- `and still answering queries.`);
2447
- }
2448
- if (result.missingVectors > 0) {
2449
- console.log(` Still without a vector: ${result.missingVectors}`);
2450
- }
2451
- if (result.failed > 0 && result.missingVectors === 0) {
2452
- console.log(` Could not be regenerated: ${result.failed} ` +
2453
- `(these still hold their previous embedding)`);
2454
- }
2455
- for (const [outcome, count] of Object.entries(result.outcomes)) {
2456
- if (outcome !== 'stored' && count > 0)
2457
- console.log(` ${outcome}: ${count}`);
2458
- }
2459
- }
2460
- else if (!result.pendingReindexCleared) {
2461
- console.log(` Note: ${result.missingVectorsDatabaseWide} memories in other namespaces still ` +
2462
- `have no vector, so the reindex-needed flag stays set.`);
2463
- }
2464
- }
2465
- if (incomplete)
2466
- process.exitCode = 1;
2467
- });
2468
- }
2469
- catch (err) {
2470
- if (err instanceof Error) {
2471
- if (opts.json)
2472
- console.log(JSON.stringify({ refused: true, reason: err.message, indexTouched: false }));
2473
- else
2474
- console.error(`❌ Reindex failed: ${err.message}`);
2475
- process.exit(1);
2476
- }
2477
- throw err;
2478
- }
1857
+ await withDatabase(() => {
1858
+ const result = reindexFts();
1859
+ console.log(opts.json ? JSON.stringify(result) : `Keyword index rebuilt (${result.entities} entities).`);
1860
+ });
2479
1861
  });
2480
1862
  program
2481
1863
  .command('status')
2482
- .description('Show MeMesh status and capabilities')
1864
+ .description('Show MeMesh status')
2483
1865
  .option('--cached', 'Use cached update info only (skip fresh npm lookup)')
2484
1866
  .action(async (opts) => {
2485
1867
  await withDatabase(() => { });
2486
- const caps = detectCapabilities();
2487
1868
  const { getCurrentInstallChannel, getInstallChannelSupport } = await import('../../core/install-channel.js');
2488
1869
  const install = getCurrentInstallChannel({ packageRoot });
2489
1870
  const installSupport = getInstallChannelSupport(install, packageRoot);
2490
1871
  const { getUpdateCheck, formatUpdateCheckStatus } = await import('../../core/version-check.js');
2491
1872
  const update = await getUpdateCheck(pkg.version, { preferFresh: !opts.cached });
2492
1873
  console.log(`MeMesh v${pkg.version}`);
2493
- console.log(`Search level: ${caps.searchLevel} (${caps.searchLevel === 1 ? 'Smart Mode' : 'Core'})`);
2494
- console.log(`Embeddings: ${caps.embeddings}`);
2495
- console.log(`LLM: ${caps.llm ? `${caps.llm.provider} (${caps.llm.model ?? 'default'})` : 'not configured'}`);
2496
1874
  console.log(`Install method: ${installSupport.label}`);
2497
1875
  for (const line of formatUpdateCheckStatus(update)) {
2498
1876
  console.log(`\n${line}`);