@pcircle/memesh 4.8.3 → 4.8.5

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 (112) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/AGENTS.md +8 -3
  5. package/README.de.md +2 -1
  6. package/README.md +2 -1
  7. package/README.zh-TW.md +2 -1
  8. package/dashboard/dist/index.html +3 -3
  9. package/dist/core/agent-message-inbox.d.ts +2 -1
  10. package/dist/core/agent-message-inbox.d.ts.map +1 -1
  11. package/dist/core/agent-message-inbox.js +23 -4
  12. package/dist/core/agent-message-inbox.js.map +1 -1
  13. package/dist/core/agent-messaging.d.ts.map +1 -1
  14. package/dist/core/agent-messaging.js +23 -15
  15. package/dist/core/agent-messaging.js.map +1 -1
  16. package/dist/core/agent-scope-id.d.ts +11 -0
  17. package/dist/core/agent-scope-id.d.ts.map +1 -0
  18. package/dist/core/agent-scope-id.js +40 -0
  19. package/dist/core/agent-scope-id.js.map +1 -0
  20. package/dist/core/briefing.d.ts.map +1 -1
  21. package/dist/core/briefing.js +8 -2
  22. package/dist/core/briefing.js.map +1 -1
  23. package/dist/core/doctor.d.ts +1 -0
  24. package/dist/core/doctor.d.ts.map +1 -1
  25. package/dist/core/doctor.js +154 -16
  26. package/dist/core/doctor.js.map +1 -1
  27. package/dist/core/dreamer.d.ts.map +1 -1
  28. package/dist/core/dreamer.js +3 -1
  29. package/dist/core/dreamer.js.map +1 -1
  30. package/dist/core/embedder.d.ts.map +1 -1
  31. package/dist/core/embedder.js +4 -1
  32. package/dist/core/embedder.js.map +1 -1
  33. package/dist/core/lifecycle.d.ts.map +1 -1
  34. package/dist/core/lifecycle.js +8 -5
  35. package/dist/core/lifecycle.js.map +1 -1
  36. package/dist/core/memory-tool.d.ts.map +1 -1
  37. package/dist/core/memory-tool.js +3 -1
  38. package/dist/core/memory-tool.js.map +1 -1
  39. package/dist/core/ollama-host.d.ts +1 -1
  40. package/dist/core/ollama-host.d.ts.map +1 -1
  41. package/dist/core/ollama-host.js +46 -14
  42. package/dist/core/ollama-host.js.map +1 -1
  43. package/dist/core/operations.d.ts +1 -1
  44. package/dist/core/operations.d.ts.map +1 -1
  45. package/dist/core/operations.js +1 -1
  46. package/dist/core/operations.js.map +1 -1
  47. package/dist/core/project-tags.d.ts +2 -0
  48. package/dist/core/project-tags.d.ts.map +1 -1
  49. package/dist/core/project-tags.js +29 -1
  50. package/dist/core/project-tags.js.map +1 -1
  51. package/dist/core/schema-export.js +3 -3
  52. package/dist/core/schema-export.js.map +1 -1
  53. package/dist/core/version-check.d.ts +1 -0
  54. package/dist/core/version-check.d.ts.map +1 -1
  55. package/dist/core/version-check.js +35 -0
  56. package/dist/core/version-check.js.map +1 -1
  57. package/dist/db.d.ts.map +1 -1
  58. package/dist/db.js +5 -2
  59. package/dist/db.js.map +1 -1
  60. package/dist/host-runtime/acp.d.ts.map +1 -1
  61. package/dist/host-runtime/acp.js +2 -1
  62. package/dist/host-runtime/acp.js.map +1 -1
  63. package/dist/host-runtime/claude.d.ts.map +1 -1
  64. package/dist/host-runtime/claude.js +6 -8
  65. package/dist/host-runtime/claude.js.map +1 -1
  66. package/dist/host-runtime/codex.d.ts.map +1 -1
  67. package/dist/host-runtime/codex.js +2 -1
  68. package/dist/host-runtime/codex.js.map +1 -1
  69. package/dist/host-runtime/entry.d.ts +5 -0
  70. package/dist/host-runtime/entry.d.ts.map +1 -0
  71. package/dist/host-runtime/entry.js +11 -0
  72. package/dist/host-runtime/entry.js.map +1 -0
  73. package/dist/knowledge-graph.d.ts +0 -1
  74. package/dist/knowledge-graph.d.ts.map +1 -1
  75. package/dist/knowledge-graph.js +27 -19
  76. package/dist/knowledge-graph.js.map +1 -1
  77. package/dist/skills-manifest.json +29 -24
  78. package/dist/storage/entity-index.d.ts +4 -0
  79. package/dist/storage/entity-index.d.ts.map +1 -0
  80. package/dist/storage/entity-index.js +15 -0
  81. package/dist/storage/entity-index.js.map +1 -0
  82. package/dist/storage/fts-index.d.ts.map +1 -1
  83. package/dist/storage/fts-index.js +5 -0
  84. package/dist/storage/fts-index.js.map +1 -1
  85. package/dist/storage/graph-repairs.d.ts +11 -1
  86. package/dist/storage/graph-repairs.d.ts.map +1 -1
  87. package/dist/storage/graph-repairs.js +172 -9
  88. package/dist/storage/graph-repairs.js.map +1 -1
  89. package/dist/transports/cli/cli.d.ts.map +1 -1
  90. package/dist/transports/cli/cli.js +23 -12
  91. package/dist/transports/cli/cli.js.map +1 -1
  92. package/dist/transports/mcp/handlers.d.ts +3 -3
  93. package/dist/transports/mcp/handlers.js +3 -3
  94. package/dist/transports/mcp/handlers.js.map +1 -1
  95. package/dist/transports/schemas.d.ts +19 -19
  96. package/dist/transports/schemas.d.ts.map +1 -1
  97. package/dist/transports/schemas.js +18 -11
  98. package/dist/transports/schemas.js.map +1 -1
  99. package/docs/platforms/README.md +1 -1
  100. package/docs/platforms/agent-messaging.md +157 -0
  101. package/hooks/hooks.json +10 -0
  102. package/package.json +9 -5
  103. package/scripts/hooks/_generated/agent-message-inbox.js +23 -4
  104. package/scripts/hooks/_generated/fts-index.js +5 -0
  105. package/scripts/hooks/_shared.js +51 -3
  106. package/scripts/hooks/decision-nudge.js +152 -0
  107. package/scripts/hooks/post-commit.js +11 -0
  108. package/scripts/hooks/pre-compact.js +12 -4
  109. package/scripts/hooks/session-start.js +24 -3
  110. package/scripts/upgrade-plugin.sh +71 -2
  111. package/skills/memesh/SKILL.md +5 -1
  112. /package/{.mcp.json → .claude-plugin/mcp.json} +0 -0
@@ -28,6 +28,7 @@ import { autoCaptureDecision } from './capture-flag.js';
28
28
  import { guardFromMetadata } from './guards.js';
29
29
  import { getAgentMessageStorageReport } from './agent-message-storage.js';
30
30
  import { readHostConfigFile } from '../host-runtime/config.js';
31
+ import { summariseTelemetry } from './llm-telemetry.js';
31
32
  const EMBEDDING_PROBE_TIMEOUT_MS = 15000;
32
33
  const EXPECTED_HOOK_TYPES = ['PreToolUse', 'SessionStart', 'PostToolUse', 'Stop', 'PreCompact'];
33
34
  const AGENT_MESSAGE_STORAGE_QUOTA_ENV = 'MEMESH_AGENT_MESSAGE_STORAGE_QUOTA_BYTES';
@@ -216,28 +217,49 @@ function inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl,
216
217
  return createCheck('config', 'Config', 'fail', `${configPath} could not be read or parsed (${msg}). Every setting in it — LLM provider, fallbacks, embedder — is being silently ignored right now.`, `Fix the JSON or remove the file to fall back to defaults: mv ${configPath} ${configPath}.bak`, { code: 'config-parse.unreadable', params: { path: configPath, detail: msg } });
217
218
  }
218
219
  }
219
- function inspectMcpConfig(packageRoot, existsSyncImpl, readFileSyncImpl) {
220
- const mcpPath = path.join(packageRoot, '.mcp.json');
220
+ const MCP_PLACEHOLDER = '${CLAUDE_PLUGIN_ROOT}';
221
+ function declaredMcpManifest(packageRoot, readFileSyncImpl) {
222
+ const parsed = parseJsonFile(path.join(packageRoot, '.claude-plugin', 'plugin.json'), readFileSyncImpl);
223
+ if (!parsed.ok)
224
+ return null;
225
+ const declared = parsed.value.mcpServers;
226
+ if (typeof declared !== 'string' || !declared.startsWith('./'))
227
+ return null;
228
+ return declared.slice(2);
229
+ }
230
+ function inspectMcpConfig(packageRoot, installChannel, existsSyncImpl, readFileSyncImpl, env) {
231
+ const relativeManifest = declaredMcpManifest(packageRoot, readFileSyncImpl);
232
+ if (relativeManifest === null) {
233
+ return createCheck('mcp-config', 'MCP config', 'fail', '.claude-plugin/plugin.json declares no `mcpServers` path, so Claude Code has no MeMesh MCP server to start.', 'Reinstall MeMesh so the plugin manifest and the MCP manifest it names are both restored.', { code: 'mcp-config.missing' });
234
+ }
235
+ const label = relativeManifest;
236
+ const mcpPath = path.join(packageRoot, relativeManifest);
221
237
  if (!existsSyncImpl(mcpPath)) {
222
- return createCheck('mcp-config', 'MCP config', 'fail', '.mcp.json is missing.', 'Restore `.mcp.json` from the package or reinstall MeMesh.', { code: 'mcp-config.missing' });
238
+ return createCheck('mcp-config', 'MCP config', 'fail', `${label} is missing.`, 'Reinstall MeMesh so the plugin manifest and the MCP manifest it names are both restored.', { code: 'mcp-config.missing' });
223
239
  }
224
240
  const parsed = parseJsonFile(mcpPath, readFileSyncImpl);
225
241
  if (!parsed.ok) {
226
- return createCheck('mcp-config', 'MCP config', 'fail', '.mcp.json is not valid JSON.', `Fix ${mcpPath} so Claude Code can read the MCP server definition.`, { code: 'mcp-config.invalid-json', params: { path: mcpPath } });
242
+ return createCheck('mcp-config', 'MCP config', 'fail', `${label} is not valid JSON.`, `Fix ${mcpPath} so Claude Code can read the MCP server definition.`, { code: 'mcp-config.invalid-json', params: { path: mcpPath } });
227
243
  }
228
244
  const server = parsed.value.mcpServers?.memesh;
229
245
  if (!server || typeof server.command !== 'string') {
230
- return createCheck('mcp-config', 'MCP config', 'fail', '.mcp.json does not define a usable `memesh` MCP server entry.', 'Reinstall MeMesh or restore the `mcpServers.memesh` entry in `.mcp.json`.', { code: 'mcp-config.no-entry' });
246
+ return createCheck('mcp-config', 'MCP config', 'fail', `${label} does not define a usable \`memesh\` MCP server entry.`, `Reinstall MeMesh or restore the \`mcpServers.memesh\` entry in \`${label}\`.`, { code: 'mcp-config.no-entry' });
231
247
  }
232
248
  const args = Array.isArray(server.args) ? server.args : [];
233
249
  const entry = typeof args[0] === 'string' ? args[0] : null;
234
250
  if (entry) {
235
- const resolved = path.resolve(entry.replaceAll('${CLAUDE_PLUGIN_ROOT}', packageRoot));
251
+ const pluginRoot = installChannel === 'plugin-marketplace' ? packageRoot : (env.CLAUDE_PLUGIN_ROOT || null);
252
+ if (entry.includes(MCP_PLACEHOLDER) && pluginRoot === null) {
253
+ return createCheck('mcp-config', 'MCP config', 'warn', `${label} starts \`${entry}\`. NOT VERIFIED: \`${MCP_PLACEHOLDER}\` is substituted by the Claude Code plugin runtime, and this is a ${installChannel} install with CLAUDE_PLUGIN_ROOT unset — so the file it names was not checked.`, `Verify it the way the plugin runtime would: CLAUDE_PLUGIN_ROOT=${packageRoot} memesh doctor`, { code: 'mcp-config.placeholder-unresolved', params: { entry, channel: installChannel } });
254
+ }
255
+ const resolved = pluginRoot === null
256
+ ? path.resolve(packageRoot, entry)
257
+ : path.resolve(entry.replaceAll(MCP_PLACEHOLDER, pluginRoot));
236
258
  if (!existsSyncImpl(resolved)) {
237
- return createCheck('mcp-config', 'MCP config', 'fail', `.mcp.json starts \`${entry}\`, and that file is not in this install — so every memesh MCP tool fails to start.`, 'Reinstall MeMesh; if you edited `.mcp.json` by hand, point it back at `${CLAUDE_PLUGIN_ROOT}/dist/mcp/server.js`.', { code: 'mcp-config.entry-missing', params: { entry, resolved } });
259
+ return createCheck('mcp-config', 'MCP config', 'fail', `${label} starts \`${entry}\`, and that file is not in this install — so every memesh MCP tool fails to start.`, `Reinstall MeMesh; if you edited \`${label}\` by hand, point it back at \`${MCP_PLACEHOLDER}/dist/mcp/server.js\`.`, { code: 'mcp-config.entry-missing', params: { entry, resolved } });
238
260
  }
239
261
  }
240
- return createCheck('mcp-config', 'MCP config', 'pass', '.mcp.json is present, defines the memesh MCP server, and the script it starts exists.');
262
+ return createCheck('mcp-config', 'MCP config', 'pass', `${label} is present, defines the memesh MCP server, and the script it starts exists.`);
241
263
  }
242
264
  function extractHookScriptPaths(hooksConfig, packageRoot) {
243
265
  const hooks = hooksConfig.hooks;
@@ -619,7 +641,32 @@ function inspectNativeBinding(packageRoot, _existsSyncImpl, probeImpl = defaultN
619
641
  }
620
642
  return createCheck('native-binding', 'SQLite and vector search', 'warn', `sqlite-vec could not be loaded: ${result.message}. Memories are still saved and found by keyword; only search by meaning is off.`, `Run: cd "${packageRoot}" && npm install --omit=dev`, { code: 'native-binding.load-failed', params: { detail: result.message, root: packageRoot } });
621
643
  }
622
- function inspectShellCli(installChannel, packageRoot, resolveShellMemeshImpl) {
644
+ function readVersionFromInstalledBinary(binaryPath, existsSyncImpl, readFileSyncImpl, realpathSyncImpl = fs.realpathSync) {
645
+ let resolved;
646
+ try {
647
+ resolved = realpathSyncImpl(binaryPath);
648
+ }
649
+ catch {
650
+ resolved = binaryPath;
651
+ }
652
+ let dir = path.dirname(resolved);
653
+ for (let depth = 0; depth < 8; depth += 1) {
654
+ const pkgPath = path.join(dir, 'package.json');
655
+ if (existsSyncImpl(pkgPath)) {
656
+ const parsed = parseJsonFile(pkgPath, readFileSyncImpl);
657
+ if (!parsed.ok)
658
+ return null;
659
+ const { name, version } = parsed.value;
660
+ return name === '@pcircle/memesh' && typeof version === 'string' ? version : null;
661
+ }
662
+ const parent = path.dirname(dir);
663
+ if (parent === dir)
664
+ return null;
665
+ dir = parent;
666
+ }
667
+ return null;
668
+ }
669
+ function inspectShellCli(installChannel, packageRoot, packageVersion, resolveShellMemeshImpl, existsSyncImpl, readFileSyncImpl) {
623
670
  const shellPath = resolveShellMemeshImpl();
624
671
  const isSameAsCurrent = shellPath ? path.resolve(shellPath).startsWith(path.resolve(packageRoot)) : false;
625
672
  const hasDistinctShellCli = !!shellPath && !isSameAsCurrent;
@@ -629,7 +676,25 @@ function inspectShellCli(installChannel, packageRoot, resolveShellMemeshImpl) {
629
676
  : 'Running from npm-global install — shell access available in this terminal.');
630
677
  }
631
678
  if (hasDistinctShellCli) {
632
- return createCheck('shell-cli', 'Shell CLI on PATH', 'pass', `\`memesh\` resolves to ${shellPath} (separate from this install at ${packageRoot}). Both paths coexist and share the same DB.`);
679
+ const shellVersion = readVersionFromInstalledBinary(shellPath, existsSyncImpl, readFileSyncImpl);
680
+ const shellIsBehind = shellVersion ? classifyBump(shellVersion, packageVersion) : null;
681
+ const thisIsBehind = shellVersion ? classifyBump(packageVersion, shellVersion) : null;
682
+ if (shellIsBehind) {
683
+ return createCheck('shell-cli', 'Shell CLI on PATH', 'warn', `\`memesh\` resolves to ${shellPath} (separate from this install at ${packageRoot}), and it is running ${shellVersion} — behind this install's ${packageVersion}. Both share the same DB, but an agent using this install and a human typing \`memesh\` in a terminal are running different code.`, 'Run `npm install -g @pcircle/memesh@latest` to bring the shell CLI up to date — a separate global install is never updated automatically by the plugin marketplace.');
684
+ }
685
+ if (thisIsBehind) {
686
+ const pluginHost = installChannel === 'plugin-marketplace' ? detectPluginHost(packageRoot) : null;
687
+ const fix = installChannel !== 'plugin-marketplace'
688
+ ? `Update this install (a ${installChannel}) to ${shellVersion} or newer via its own channel — see \`memesh status\`.`
689
+ : pluginHost
690
+ ? `Run \`${PLUGIN_REFRESH_COMMANDS[pluginHost]}\` to bring this plugin copy to ${shellVersion} (or newer).`
691
+ : `Bring this plugin copy to ${shellVersion} (or newer) with your host's refresh command — `
692
+ + `Claude Code: \`${PLUGIN_REFRESH_COMMANDS['claude-code']}\`; `
693
+ + `Codex: \`${PLUGIN_REFRESH_COMMANDS.codex}\`.`;
694
+ return createCheck('shell-cli', 'Shell CLI on PATH', 'warn', `\`memesh\` resolves to ${shellPath} (separate from this install at ${packageRoot}), and it is running ${shellVersion} — ahead of this install's ${packageVersion}.`, fix);
695
+ }
696
+ return createCheck('shell-cli', 'Shell CLI on PATH', 'pass', `\`memesh\` resolves to ${shellPath} (separate from this install at ${packageRoot}). Both paths coexist and share the same DB`
697
+ + (shellVersion ? `, both on ${packageVersion}.` : ' — could not read the shell copy\'s own version to compare.'));
633
698
  }
634
699
  if (installChannel === 'plugin-marketplace') {
635
700
  const host = detectPluginHost(packageRoot) === 'codex' ? 'Codex CLI' : 'Claude Code';
@@ -827,6 +892,33 @@ function inspectPluginCacheCurrency(installChannel, pluginHost, packageRoot, ins
827
892
  }
828
893
  return createCheck('plugin-cache', `Plugin cache source record is current (${hostLabel})`, 'warn', `The plugin cache records commit ${installedSha.slice(0, 8)}, but the marketplace has moved to ${marketplaceSha.slice(0, 8)} under the same version — ${hostLabel} does not normally refresh a cache whose version did not change, so refresh the cache before relying on the newer marketplace code.`, `Run \`${command}\` to refresh the cache in place, then restart ${hostLabel}.`, { code: 'plugin-cache.stale', params: { installed: installedSha.slice(0, 8), marketplace: marketplaceSha.slice(0, 8), host: hostLabel, command } });
829
894
  }
895
+ function annotateNpmGlobalPluginCacheVersion(check, discoveredPackageRoot, hostLabel, runningVersion) {
896
+ const cacheRoot = path.dirname(discoveredPackageRoot);
897
+ const discoveredVersion = path.basename(discoveredPackageRoot);
898
+ let amended = check;
899
+ if (discoveredVersion !== runningVersion && classifyBump(runningVersion, discoveredVersion)) {
900
+ const skewNote = `This npm-global install is on ${runningVersion}; the ${hostLabel} plugin cache is on ${discoveredVersion}. `
901
+ + 'The plugin marketplace\'s own auto-updater only ever refreshes its plugin copy — it cannot and will not update this separate npm-global install.';
902
+ const skewFix = `Run \`memesh update\` to bring this npm-global install to ${discoveredVersion} (or newer) — it does not update itself automatically.`;
903
+ amended = {
904
+ ...amended,
905
+ status: amended.status === 'pass' ? 'warn' : amended.status,
906
+ summary: `${amended.summary} ${skewNote}`,
907
+ fix: amended.fix ? `${amended.fix} Also: ${skewFix}` : skewFix,
908
+ code: undefined,
909
+ params: undefined,
910
+ };
911
+ }
912
+ const cachedVersions = versionedPluginCacheRoots(cacheRoot);
913
+ if (cachedVersions.length > 2) {
914
+ amended = {
915
+ ...amended,
916
+ summary: `${amended.summary} ${cachedVersions.length} versioned copies of the ${hostLabel} plugin are cached under ${cacheRoot}; old ones are never removed automatically. `
917
+ + `Delete ones you no longer need once no ${hostLabel} process is using them, e.g. \`rm -rf "${cachedVersions[0]}"\`.`,
918
+ };
919
+ }
920
+ return amended;
921
+ }
830
922
  function inspectClaudeChannelRegistration(existsSyncImpl, readFileSyncImpl) {
831
923
  const configPath = path.join(homeDir(), '.claude.json');
832
924
  let parsed = null;
@@ -977,7 +1069,13 @@ async function inspectUpdateStatus(packageVersion, getUpdateCheckImpl, installSu
977
1069
  return createCheck('update-status', 'Update status', 'pass', `Running pre-release version (${packageVersion}), npm latest is ${update.latestVersion}`);
978
1070
  }
979
1071
  }
980
- return createCheck('update-status', 'Update status', update.freshness === 'stale' ? 'warn' : 'pass', `Version ${packageVersion} is current${update.freshness === 'stale' ? ', but cached update data is stale.' : '.'}`, update.freshness === 'stale'
1072
+ const checkedHoursAgo = update.lastSuccessfulCheckAt === null
1073
+ ? null
1074
+ : (Date.now() - Date.parse(update.lastSuccessfulCheckAt)) / 3600_000;
1075
+ const checkedAgo = formatHoursAgo(checkedHoursAgo);
1076
+ return createCheck('update-status', 'Update status', update.freshness === 'stale' ? 'warn' : 'pass', update.freshness === 'stale'
1077
+ ? `As of the last check (${checkedAgo}), ${packageVersion} was the latest version — that check is more than 24h old, so a newer release may exist since. Doctor never makes a live registry call itself.`
1078
+ : `As of the last check (${checkedAgo}), ${packageVersion} was the latest version.`, update.freshness === 'stale'
981
1079
  ? 'Run `memesh status` while online to refresh cached update metadata.'
982
1080
  : undefined, update.freshness === 'stale'
983
1081
  ? { code: 'update-status.stale', params: { version: packageVersion } }
@@ -1175,6 +1273,33 @@ async function inspectEmbeddingProbe(capabilities, probeCapabilities, embedTextI
1175
1273
  clearTimeout(timer);
1176
1274
  }
1177
1275
  }
1276
+ const LLM_TELEMETRY_HEALTH_WINDOW_DAYS = 7;
1277
+ const LLM_TELEMETRY_HEALTH_MIN_CALLS = 3;
1278
+ function inspectLlmTelemetryHealth(db, windowDays = LLM_TELEMETRY_HEALTH_WINDOW_DAYS, minCalls = LLM_TELEMETRY_HEALTH_MIN_CALLS) {
1279
+ let summaries;
1280
+ try {
1281
+ summaries = summariseTelemetry(windowDays, db);
1282
+ }
1283
+ catch {
1284
+ return undefined;
1285
+ }
1286
+ if (summaries.length === 0)
1287
+ return undefined;
1288
+ const broken = summaries
1289
+ .filter((s) => s.total_calls >= minCalls && s.successes === 0)
1290
+ .sort((a, b) => b.total_calls - a.total_calls);
1291
+ if (broken.length > 0) {
1292
+ const detail = broken.map((s) => `${s.flow} (${s.total_calls} call${s.total_calls === 1 ? '' : 's'}, 0 succeeded)`).join(', ');
1293
+ return createCheck('llm_telemetry_health', 'AI feature health', 'warn', `${broken.length} AI-backed feature${broken.length === 1 ? '' : 's'} failed every call in the last `
1294
+ + `${windowDays} days: ${detail}. Those features are silently doing nothing.`, 'Run `memesh telemetry` for the full per-flow detail, then check the model/provider configured for '
1295
+ + 'the failing flow. `memesh doctor --probe` confirms whether it answers a live call.', { code: 'llm-telemetry.silent-failure', params: { count: broken.length, detail, windowDays } });
1296
+ }
1297
+ const totalCalls = summaries.reduce((n, s) => n + s.total_calls, 0);
1298
+ const totalSuccesses = summaries.reduce((n, s) => n + s.successes, 0);
1299
+ const rate = totalCalls > 0 ? Math.round((totalSuccesses / totalCalls) * 100) : 100;
1300
+ return createInfo('llm_telemetry_health', 'AI feature health', `${summaries.length} AI-backed flow${summaries.length === 1 ? '' : 's'} made ${totalCalls} call(s) in the `
1301
+ + `last ${windowDays} days; ${rate}% succeeded.`);
1302
+ }
1178
1303
  async function inspectLlmProbe(capabilities, probeCapabilities, probeProviderImpl) {
1179
1304
  const llm = capabilities.llm;
1180
1305
  if (!llm) {
@@ -1204,7 +1329,7 @@ function summarizeOverallStatus(checks) {
1204
1329
  return 'PASS';
1205
1330
  }
1206
1331
  export async function runDoctor(options) {
1207
- const { packageRoot, packageVersion, probeHttp = false, probeCapabilities = false, embedTextImpl = embedText, probeProviderImpl = probeProvider, httpBaseUrl = 'http://127.0.0.1:3737', platform = process.platform, openDatabaseImpl = openDatabase, closeDatabaseImpl = closeDatabase, isDatabaseOpenImpl = isDatabaseOpen, detectCapabilitiesImpl = detectCapabilities, getConfigPathImpl = getConfigPath, getUpdateCheckImpl = getUpdateCheck, getCurrentInstallChannelImpl = getCurrentInstallChannel, installedPluginsPathImpl, marketplaceHeadShaImpl = defaultMarketplaceHeadSha, pluginCacheDiscoveryImpl, getInstallChannelSupportImpl = getInstallChannelSupport, existsSyncImpl = fs.existsSync, readFileSyncImpl = fs.readFileSync, statSyncImpl = fs.statSync, fetchImpl = fetch, agentMessageStoragePolicy, nativeBindingProbeImpl, resolveShellMemeshImpl = defaultResolveShellMemesh, probeMessageCapability = process.env.MEMESH_DOCTOR_PROBE_MESSAGE_CAPABILITY === '1', messageCapabilityProbeImpl = probeInstalledMessageCapability, probeMessageRouterStatus = process.env.MEMESH_DOCTOR_PROBE_MESSAGE_ROUTER === '1', messageRouterStatusProbeImpl = defaultMessageRouterStatusProbe, } = options;
1332
+ const { packageRoot, packageVersion, probeHttp = false, probeCapabilities = false, embedTextImpl = embedText, probeProviderImpl = probeProvider, httpBaseUrl = 'http://127.0.0.1:3737', platform = process.platform, envImpl = process.env, openDatabaseImpl = openDatabase, closeDatabaseImpl = closeDatabase, isDatabaseOpenImpl = isDatabaseOpen, detectCapabilitiesImpl = detectCapabilities, getConfigPathImpl = getConfigPath, getUpdateCheckImpl = getUpdateCheck, getCurrentInstallChannelImpl = getCurrentInstallChannel, installedPluginsPathImpl, marketplaceHeadShaImpl = defaultMarketplaceHeadSha, pluginCacheDiscoveryImpl, getInstallChannelSupportImpl = getInstallChannelSupport, existsSyncImpl = fs.existsSync, readFileSyncImpl = fs.readFileSync, statSyncImpl = fs.statSync, fetchImpl = fetch, agentMessageStoragePolicy, nativeBindingProbeImpl, resolveShellMemeshImpl = defaultResolveShellMemesh, probeMessageCapability = process.env.MEMESH_DOCTOR_PROBE_MESSAGE_CAPABILITY === '1', messageCapabilityProbeImpl = probeInstalledMessageCapability, probeMessageRouterStatus = process.env.MEMESH_DOCTOR_PROBE_MESSAGE_ROUTER === '1', messageRouterStatusProbeImpl = defaultMessageRouterStatusProbe, } = options;
1208
1333
  const wasDbOpenBeforeUs = isDatabaseOpenImpl();
1209
1334
  const safeCloseDatabaseImpl = wasDbOpenBeforeUs
1210
1335
  ? () => undefined
@@ -1259,7 +1384,14 @@ export async function runDoctor(options) {
1259
1384
  ? 'The vector index could not be read, so how much of your memory semantic recall can see is unknown'
1260
1385
  : `${missingVectors} memor${missingVectors === 1 ? 'y has' : 'ies have'} no search vector, `
1261
1386
  + 'so semantic recall cannot find them (keyword search still works)';
1262
- dbChecks.push(createCheck('vector_index', 'Vector Index', 'warn', owed, `Run 'memesh reindex' to fix. This will restore full search functionality.`, { code: 'vector-index.stale', params: { missing: missingVectors ?? -1 } }));
1387
+ const noEmbedderConfigured = detectCapabilitiesImpl().embeddings === 'tfidf';
1388
+ const vectorIndexFix = noEmbedderConfigured
1389
+ ? `No embedder is configured, so reindex has nothing to embed with — run 'memesh config set embedder.provider ollama' (or 'openai') first, then 'memesh reindex'.`
1390
+ : `Run 'memesh reindex' to fix. This will restore full search functionality.`;
1391
+ dbChecks.push(createCheck('vector_index', 'Vector Index', 'warn', owed, vectorIndexFix, {
1392
+ code: noEmbedderConfigured ? 'vector-index.stale-no-embedder' : 'vector-index.stale',
1393
+ params: { missing: missingVectors ?? -1 },
1394
+ }));
1263
1395
  }
1264
1396
  const generation = readVectorGeneration();
1265
1397
  if (generation.state !== 'none') {
@@ -1340,6 +1472,9 @@ export async function runDoctor(options) {
1340
1472
  dbChecks.push(createInfo('citation_compliance', 'Memory citation rate', `${cited} of ${citationTotal} session(s) with injected memories cited at least one (${rate}%).`));
1341
1473
  }
1342
1474
  }
1475
+ const llmTelemetryHealth = inspectLlmTelemetryHealth(db);
1476
+ if (llmTelemetryHealth)
1477
+ dbChecks.push(llmTelemetryHealth);
1343
1478
  }
1344
1479
  catch (err) {
1345
1480
  const message = err instanceof Error ? err.message : 'unknown database error';
@@ -1407,7 +1542,7 @@ export async function runDoctor(options) {
1407
1542
  }
1408
1543
  }
1409
1544
  checks.push(inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl, detectCapabilitiesImpl().llm));
1410
- checks.push(inspectMcpConfig(packageRoot, existsSyncImpl, readFileSyncImpl));
1545
+ checks.push(inspectMcpConfig(packageRoot, install, existsSyncImpl, readFileSyncImpl, envImpl));
1411
1546
  checks.push(...inspectHooksConfig(packageRoot, platform, existsSyncImpl, readFileSyncImpl, statSyncImpl));
1412
1547
  const pluginHost = detectPluginHost(packageRoot);
1413
1548
  let codexPluginCacheDetected = pluginHost === 'codex';
@@ -1426,9 +1561,12 @@ export async function runDoctor(options) {
1426
1561
  codexPluginCacheDetected = true;
1427
1562
  if (discovered.host === 'claude-code')
1428
1563
  claudePluginCacheDetected = true;
1429
- const check = discovered.unverifiableReason
1564
+ let check = discovered.unverifiableReason
1430
1565
  ? pluginCacheUnverifiable(discovered.host, discovered.unverifiableReason)
1431
1566
  : inspectPluginCacheCurrency('plugin-marketplace', discovered.host, discovered.packageRoot, discovered.installedPluginsPath, readFileSyncImpl, existsSyncImpl, marketplaceHeadShaImpl);
1567
+ if (check && !discovered.unverifiableReason) {
1568
+ check = annotateNpmGlobalPluginCacheVersion(check, discovered.packageRoot, discovered.host === 'codex' ? 'Codex' : 'Claude Code', packageVersion);
1569
+ }
1432
1570
  if (check) {
1433
1571
  const hostName = discovered.host;
1434
1572
  const index = (discoveredCounts.get(discovered.host) ?? 0) + 1;
@@ -1450,7 +1588,7 @@ export async function runDoctor(options) {
1450
1588
  checks.push(inspectDashboardArtifact(packageRoot, existsSyncImpl));
1451
1589
  checks.push(inspectNodeRuntime(packageRoot, existsSyncImpl, readFileSyncImpl));
1452
1590
  checks.push(inspectNativeBinding(packageRoot, existsSyncImpl, nativeBindingProbeImpl));
1453
- checks.push(inspectShellCli(install, packageRoot, resolveShellMemeshImpl));
1591
+ checks.push(inspectShellCli(install, packageRoot, packageVersion, resolveShellMemeshImpl, existsSyncImpl, readFileSyncImpl));
1454
1592
  checks.push(verifySkillsManifest(packageRoot, existsSyncImpl, readFileSyncImpl, installSupport));
1455
1593
  checks.push(inspectMessageCapability(packageRoot, probeMessageCapability, messageCapabilityProbeImpl));
1456
1594
  checks.push(await inspectMessageRouterStatus(probeMessageRouterStatus, messageRouterStatusProbeImpl));