@pcircle/memesh 4.8.3 → 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 (254) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.codex-plugin/mcp.json +6 -4
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/AGENTS.md +68 -17
  6. package/README.de.md +83 -555
  7. package/README.md +83 -581
  8. package/README.zh-TW.md +84 -572
  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-message-inbox.d.ts +2 -1
  14. package/dist/core/agent-message-inbox.d.ts.map +1 -1
  15. package/dist/core/agent-message-inbox.js +23 -4
  16. package/dist/core/agent-message-inbox.js.map +1 -1
  17. package/dist/core/agent-messaging.d.ts.map +1 -1
  18. package/dist/core/agent-messaging.js +23 -15
  19. package/dist/core/agent-messaging.js.map +1 -1
  20. package/dist/core/agent-router.d.ts +11 -10
  21. package/dist/core/agent-router.d.ts.map +1 -1
  22. package/dist/core/agent-router.js +29 -14
  23. package/dist/core/agent-router.js.map +1 -1
  24. package/dist/core/agent-scope-id.d.ts +11 -0
  25. package/dist/core/agent-scope-id.d.ts.map +1 -0
  26. package/dist/core/agent-scope-id.js +40 -0
  27. package/dist/core/agent-scope-id.js.map +1 -0
  28. package/dist/core/analytics.d.ts.map +1 -1
  29. package/dist/core/analytics.js.map +1 -1
  30. package/dist/core/briefing.d.ts.map +1 -1
  31. package/dist/core/briefing.js +8 -2
  32. package/dist/core/briefing.js.map +1 -1
  33. package/dist/core/config.d.ts +4 -40
  34. package/dist/core/config.d.ts.map +1 -1
  35. package/dist/core/config.js +75 -141
  36. package/dist/core/config.js.map +1 -1
  37. package/dist/core/demo.d.ts.map +1 -1
  38. package/dist/core/demo.js +6 -6
  39. package/dist/core/demo.js.map +1 -1
  40. package/dist/core/doctor.d.ts +2 -6
  41. package/dist/core/doctor.d.ts.map +1 -1
  42. package/dist/core/doctor.js +120 -154
  43. package/dist/core/doctor.js.map +1 -1
  44. package/dist/core/dreamer.d.ts +32 -47
  45. package/dist/core/dreamer.d.ts.map +1 -1
  46. package/dist/core/dreamer.js +214 -704
  47. package/dist/core/dreamer.js.map +1 -1
  48. package/dist/core/install-channel.d.ts.map +1 -1
  49. package/dist/core/install-channel.js +4 -47
  50. package/dist/core/install-channel.js.map +1 -1
  51. package/dist/core/install-id.d.ts.map +1 -1
  52. package/dist/core/install-id.js.map +1 -1
  53. package/dist/core/kg-backfill.d.ts.map +1 -1
  54. package/dist/core/kg-backfill.js.map +1 -1
  55. package/dist/core/lesson-engine.d.ts +0 -5
  56. package/dist/core/lesson-engine.d.ts.map +1 -1
  57. package/dist/core/lesson-engine.js +0 -25
  58. package/dist/core/lesson-engine.js.map +1 -1
  59. package/dist/core/lifecycle.d.ts.map +1 -1
  60. package/dist/core/lifecycle.js +58 -49
  61. package/dist/core/lifecycle.js.map +1 -1
  62. package/dist/core/memory-tool.d.ts.map +1 -1
  63. package/dist/core/memory-tool.js +20 -18
  64. package/dist/core/memory-tool.js.map +1 -1
  65. package/dist/core/operations.d.ts +3 -27
  66. package/dist/core/operations.d.ts.map +1 -1
  67. package/dist/core/operations.js +10 -245
  68. package/dist/core/operations.js.map +1 -1
  69. package/dist/core/paths.d.ts +4 -1
  70. package/dist/core/paths.d.ts.map +1 -1
  71. package/dist/core/paths.js +88 -14
  72. package/dist/core/paths.js.map +1 -1
  73. package/dist/core/product-improvements.js +2 -2
  74. package/dist/core/product-improvements.js.map +1 -1
  75. package/dist/core/project-tags.d.ts +2 -0
  76. package/dist/core/project-tags.d.ts.map +1 -1
  77. package/dist/core/project-tags.js +29 -1
  78. package/dist/core/project-tags.js.map +1 -1
  79. package/dist/core/schema-export.d.ts.map +1 -1
  80. package/dist/core/schema-export.js +13 -3
  81. package/dist/core/schema-export.js.map +1 -1
  82. package/dist/core/semver.d.ts +7 -0
  83. package/dist/core/semver.d.ts.map +1 -0
  84. package/dist/core/semver.js +49 -0
  85. package/dist/core/semver.js.map +1 -0
  86. package/dist/core/serializer.d.ts.map +1 -1
  87. package/dist/core/serializer.js +69 -57
  88. package/dist/core/serializer.js.map +1 -1
  89. package/dist/core/signal-scorer.d.ts.map +1 -1
  90. package/dist/core/signal-scorer.js.map +1 -1
  91. package/dist/core/transcript-extractor.d.ts +1 -85
  92. package/dist/core/transcript-extractor.d.ts.map +1 -1
  93. package/dist/core/transcript-extractor.js +5 -364
  94. package/dist/core/transcript-extractor.js.map +1 -1
  95. package/dist/core/transcript-source.d.ts +22 -6
  96. package/dist/core/transcript-source.d.ts.map +1 -1
  97. package/dist/core/transcript-source.js +108 -69
  98. package/dist/core/transcript-source.js.map +1 -1
  99. package/dist/core/types.d.ts +1 -17
  100. package/dist/core/types.d.ts.map +1 -1
  101. package/dist/core/version-check.d.ts +1 -0
  102. package/dist/core/version-check.d.ts.map +1 -1
  103. package/dist/core/version-check.js +46 -1
  104. package/dist/core/version-check.js.map +1 -1
  105. package/dist/db.d.ts +0 -34
  106. package/dist/db.d.ts.map +1 -1
  107. package/dist/db.js +6 -287
  108. package/dist/db.js.map +1 -1
  109. package/dist/host-runtime/acp.d.ts.map +1 -1
  110. package/dist/host-runtime/acp.js +4 -3
  111. package/dist/host-runtime/acp.js.map +1 -1
  112. package/dist/host-runtime/claude.d.ts.map +1 -1
  113. package/dist/host-runtime/claude.js +11 -11
  114. package/dist/host-runtime/claude.js.map +1 -1
  115. package/dist/host-runtime/codex-session.d.ts +9 -1
  116. package/dist/host-runtime/codex-session.d.ts.map +1 -1
  117. package/dist/host-runtime/codex-session.js +474 -29
  118. package/dist/host-runtime/codex-session.js.map +1 -1
  119. package/dist/host-runtime/codex.d.ts.map +1 -1
  120. package/dist/host-runtime/codex.js +4 -3
  121. package/dist/host-runtime/codex.js.map +1 -1
  122. package/dist/host-runtime/config.d.ts +1 -0
  123. package/dist/host-runtime/config.d.ts.map +1 -1
  124. package/dist/host-runtime/config.js +4 -0
  125. package/dist/host-runtime/config.js.map +1 -1
  126. package/dist/host-runtime/entry.d.ts +5 -0
  127. package/dist/host-runtime/entry.d.ts.map +1 -0
  128. package/dist/host-runtime/entry.js +11 -0
  129. package/dist/host-runtime/entry.js.map +1 -0
  130. package/dist/host-runtime/router-client.d.ts.map +1 -1
  131. package/dist/host-runtime/router-client.js +62 -15
  132. package/dist/host-runtime/router-client.js.map +1 -1
  133. package/dist/host-runtime/router.js +2 -2
  134. package/dist/host-runtime/router.js.map +1 -1
  135. package/dist/knowledge-graph.d.ts +0 -1
  136. package/dist/knowledge-graph.d.ts.map +1 -1
  137. package/dist/knowledge-graph.js +77 -60
  138. package/dist/knowledge-graph.js.map +1 -1
  139. package/dist/mcp/THIRD_PARTY_NOTICES.txt +217 -0
  140. package/dist/mcp/server.js +30685 -38
  141. package/dist/mcp/server.js.map +6 -1
  142. package/dist/skills-manifest.json +39 -34
  143. package/dist/storage/entity-index.d.ts +3 -0
  144. package/dist/storage/entity-index.d.ts.map +1 -0
  145. package/dist/storage/entity-index.js +8 -0
  146. package/dist/storage/entity-index.js.map +1 -0
  147. package/dist/storage/fts-index.d.ts.map +1 -1
  148. package/dist/storage/fts-index.js +11 -7
  149. package/dist/storage/fts-index.js.map +1 -1
  150. package/dist/storage/graph-repairs.d.ts +7 -2
  151. package/dist/storage/graph-repairs.d.ts.map +1 -1
  152. package/dist/storage/graph-repairs.js +89 -13
  153. package/dist/storage/graph-repairs.js.map +1 -1
  154. package/dist/storage/schema.d.ts +1 -1
  155. package/dist/storage/schema.d.ts.map +1 -1
  156. package/dist/storage/schema.js +1 -2
  157. package/dist/storage/schema.js.map +1 -1
  158. package/dist/storage/sqlite.d.ts +0 -1
  159. package/dist/storage/sqlite.d.ts.map +1 -1
  160. package/dist/storage/sqlite.js.map +1 -1
  161. package/dist/transports/agent-messaging.d.ts.map +1 -1
  162. package/dist/transports/agent-messaging.js +5 -7
  163. package/dist/transports/agent-messaging.js.map +1 -1
  164. package/dist/transports/cli/cli.d.ts.map +1 -1
  165. package/dist/transports/cli/cli.js +75 -686
  166. package/dist/transports/cli/cli.js.map +1 -1
  167. package/dist/transports/http/retired-routes.js +1 -1
  168. package/dist/transports/http/retired-routes.js.map +1 -1
  169. package/dist/transports/http/server.d.ts.map +1 -1
  170. package/dist/transports/http/server.js +13 -232
  171. package/dist/transports/http/server.js.map +1 -1
  172. package/dist/transports/mcp/handlers.d.ts +130 -4
  173. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  174. package/dist/transports/mcp/handlers.js +56 -7
  175. package/dist/transports/mcp/handlers.js.map +1 -1
  176. package/dist/transports/schemas.d.ts +71 -19
  177. package/dist/transports/schemas.d.ts.map +1 -1
  178. package/dist/transports/schemas.js +57 -11
  179. package/dist/transports/schemas.js.map +1 -1
  180. package/docs/platforms/README.md +6 -5
  181. package/docs/platforms/agent-messaging.md +241 -20
  182. package/hooks/hooks.json +23 -2
  183. package/llms-install.md +62 -30
  184. package/package.json +12 -9
  185. package/scripts/hooks/_generated/agent-message-inbox.js +23 -4
  186. package/scripts/hooks/_generated/core-paths.js +88 -14
  187. package/scripts/hooks/_generated/fts-index.js +11 -7
  188. package/scripts/hooks/_generated/schema.js +1 -2
  189. package/scripts/hooks/_shared.js +65 -23
  190. package/scripts/hooks/decision-nudge.js +152 -0
  191. package/scripts/hooks/post-commit.js +11 -0
  192. package/scripts/hooks/pre-compact.js +12 -4
  193. package/scripts/hooks/session-start.js +32 -9
  194. package/scripts/hooks/session-summary.js +19 -374
  195. package/scripts/upgrade-plugin.sh +71 -2
  196. package/skills/memesh/SKILL.md +24 -15
  197. package/skills/memesh-review/SKILL.md +7 -6
  198. package/dist/core/auto-tagger.d.ts +0 -10
  199. package/dist/core/auto-tagger.d.ts.map +0 -1
  200. package/dist/core/auto-tagger.js +0 -63
  201. package/dist/core/auto-tagger.js.map +0 -1
  202. package/dist/core/conflict-candidates.d.ts +0 -20
  203. package/dist/core/conflict-candidates.d.ts.map +0 -1
  204. package/dist/core/conflict-candidates.js +0 -71
  205. package/dist/core/conflict-candidates.js.map +0 -1
  206. package/dist/core/conflict-judge.d.ts +0 -58
  207. package/dist/core/conflict-judge.d.ts.map +0 -1
  208. package/dist/core/conflict-judge.js +0 -189
  209. package/dist/core/conflict-judge.js.map +0 -1
  210. package/dist/core/digest-validator.d.ts +0 -18
  211. package/dist/core/digest-validator.d.ts.map +0 -1
  212. package/dist/core/digest-validator.js +0 -85
  213. package/dist/core/digest-validator.js.map +0 -1
  214. package/dist/core/embedder.d.ts +0 -20
  215. package/dist/core/embedder.d.ts.map +0 -1
  216. package/dist/core/embedder.js +0 -242
  217. package/dist/core/embedder.js.map +0 -1
  218. package/dist/core/failure-analyzer.d.ts +0 -19
  219. package/dist/core/failure-analyzer.d.ts.map +0 -1
  220. package/dist/core/failure-analyzer.js +0 -83
  221. package/dist/core/failure-analyzer.js.map +0 -1
  222. package/dist/core/json-utils.d.ts +0 -3
  223. package/dist/core/json-utils.d.ts.map +0 -1
  224. package/dist/core/json-utils.js +0 -46
  225. package/dist/core/json-utils.js.map +0 -1
  226. package/dist/core/llm-client.d.ts +0 -22
  227. package/dist/core/llm-client.d.ts.map +0 -1
  228. package/dist/core/llm-client.js +0 -203
  229. package/dist/core/llm-client.js.map +0 -1
  230. package/dist/core/llm-telemetry.d.ts +0 -47
  231. package/dist/core/llm-telemetry.d.ts.map +0 -1
  232. package/dist/core/llm-telemetry.js +0 -117
  233. package/dist/core/llm-telemetry.js.map +0 -1
  234. package/dist/core/llm-validator.d.ts +0 -20
  235. package/dist/core/llm-validator.d.ts.map +0 -1
  236. package/dist/core/llm-validator.js +0 -231
  237. package/dist/core/llm-validator.js.map +0 -1
  238. package/dist/core/ollama-host.d.ts +0 -6
  239. package/dist/core/ollama-host.d.ts.map +0 -1
  240. package/dist/core/ollama-host.js +0 -30
  241. package/dist/core/ollama-host.js.map +0 -1
  242. package/dist/core/output-language.d.ts +0 -6
  243. package/dist/core/output-language.d.ts.map +0 -1
  244. package/dist/core/output-language.js +0 -25
  245. package/dist/core/output-language.js.map +0 -1
  246. package/dist/core/prompt-safety.d.ts +0 -4
  247. package/dist/core/prompt-safety.d.ts.map +0 -1
  248. package/dist/core/prompt-safety.js +0 -20
  249. package/dist/core/prompt-safety.js.map +0 -1
  250. package/dist/storage/vector-index.d.ts +0 -3
  251. package/dist/storage/vector-index.d.ts.map +0 -1
  252. package/dist/storage/vector-index.js +0 -13
  253. package/dist/storage/vector-index.js.map +0 -1
  254. /package/{.mcp.json → .claude-plugin/mcp.json} +0 -0
@@ -4,22 +4,16 @@ import path from 'path';
4
4
  import net from 'node:net';
5
5
  import { createHash } from 'crypto';
6
6
  import { createRequire } from 'module';
7
- import { pathToFileURL } from 'url';
8
7
  import { execFileSync } from 'child_process';
9
- import { detectCapabilities, getConfigPath, isTranscriptMiningEnabled, readConfig } from './config.js';
10
- import { embedText } from './embedder.js';
11
- import { probeProvider } from './llm-validator.js';
12
- import { openDatabase, closeDatabase, getPendingReindexInfo, isDatabaseOpen, readVectorGeneration, generationRowIds, } from '../db.js';
8
+ import { findRetiredConfigKeys, getConfigPath, readConfig } from './config.js';
9
+ import { openDatabase, closeDatabase, isDatabaseOpen, } from '../db.js';
13
10
  import { getUpdateCheck } from './version-check.js';
14
11
  import { classifyBump } from './updater.js';
15
12
  import { getCurrentInstallChannel, getInstallChannelSupport, detectPluginHost, pluginHostConfigRoot, versionedPluginCacheRoots, PLUGIN_REFRESH_COMMANDS, } from './install-channel.js';
16
13
  import { getInstallRecord } from './install-id.js';
17
14
  import { citationRulePath, citationRuleState } from './citation-rule.js';
18
- import { getDbPath, getMemeshDirFromDbPath, homeDir, memeshDir, getProjectName } from './paths.js';
15
+ import { getAgentRouterSocketPath, getDbPath, getMemeshDirFromDbPath, homeDir, memeshDir } from './paths.js';
19
16
  import { detectPluginRuntime, readInstallMarker } from './install-hooks.js';
20
- import { lastTranscriptMineAt } from './transcript-source.js';
21
- import { countMissingVectors } from './operations.js';
22
- import { hasVectorIndex } from '../storage/vector-index.js';
23
17
  import { UNSPACED_SCRIPT_GLOB_RUN3 } from '../storage/fts-index.js';
24
18
  import { MemeshDatabase } from '../storage/sqlite.js';
25
19
  import { AUTO_CAPTURE_TAG } from './types.js';
@@ -28,7 +22,6 @@ import { autoCaptureDecision } from './capture-flag.js';
28
22
  import { guardFromMetadata } from './guards.js';
29
23
  import { getAgentMessageStorageReport } from './agent-message-storage.js';
30
24
  import { readHostConfigFile } from '../host-runtime/config.js';
31
- const EMBEDDING_PROBE_TIMEOUT_MS = 15000;
32
25
  const EXPECTED_HOOK_TYPES = ['PreToolUse', 'SessionStart', 'PostToolUse', 'Stop', 'PreCompact'];
33
26
  const AGENT_MESSAGE_STORAGE_QUOTA_ENV = 'MEMESH_AGENT_MESSAGE_STORAGE_QUOTA_BYTES';
34
27
  const LOCALE_README_FILES = [
@@ -153,9 +146,9 @@ function inspectCodexSessionSetup(codexPluginCacheDetected, existsSyncImpl) {
153
146
  return null;
154
147
  const configPath = path.join(getMemeshDirFromDbPath(), 'hosts', 'codex-session.json');
155
148
  if (existsSyncImpl(configPath)) {
156
- return createCheck('codex-session-setup', 'Codex ordinary-session notification setup', 'pass', 'A Codex plugin cache copy was detected, but this proves only that cached source exists, not that the plugin is enabled or registered. The explicit opt-in ordinary-session notification setup is present; durable inbox remains available, and MeMesh will not auto-attach.');
149
+ return createInfo('codex-session-setup', 'Codex ordinary-session notifications', 'A Codex identity override file is present and will be validated at SessionStart. A valid override applies only in its configured workspace; other Codex plugin sessions use automatic thread-scoped identities. A cached plugin copy does not prove a live registration; use `memesh message discover --project <project>` to read current presence.');
157
150
  }
158
- return createCheck('codex-session-setup', 'Codex ordinary-session notification setup', 'warn', 'A Codex plugin cache copy was detected, but this proves only that cached source exists, not that the plugin is enabled or registered. Durable inbox remains available, but live ordinary-session wakeup is inactive. Setup is explicit opt-in; MeMesh will not auto-attach.', 'Run `memesh agent setup codex-session --project <project> --principal <principal> --workspace <exact-workspace>`, then restart Codex.', { code: 'codex-session.config-missing' });
151
+ return createInfo('codex-session-setup', 'Codex ordinary-session notifications', 'No explicit identity override is configured. When the Codex plugin is enabled, each startup or resumed thread registers automatically under a thread-scoped identity. A cached plugin copy does not prove a live registration; use `memesh message discover --project <project>` to read current presence.');
159
152
  }
160
153
  function configuredAgentMessageStoragePolicy(explicit) {
161
154
  if (explicit !== undefined)
@@ -194,14 +187,10 @@ function parseJsonFile(filePath, readFileSyncImpl) {
194
187
  };
195
188
  }
196
189
  }
197
- function inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl, envLlm) {
190
+ function inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl) {
198
191
  const configPath = getConfigPathImpl();
199
192
  if (!existsSyncImpl(configPath)) {
200
- return createCheck('config', 'Config', 'pass', envLlm
201
- ? `No config file yet (${configPath}), but your environment names ${envLlm.provider}${envLlm.apiKey ? ' (via its API key)' : ' (via OLLAMA_HOST)'}, which enables Smart Mode. A file is only needed to pin a provider or change defaults.`
202
- : `No config file yet (${configPath}). MeMesh will run in Core mode until you configure Smart Mode.`, envLlm
203
- ? `Optional: \`memesh config set llm.provider ${envLlm.provider}\` pins it so it does not depend on which shell you run from.`
204
- : 'Optional: run `memesh config list` or set an LLM with `memesh config set llm.provider anthropic`.');
193
+ return createCheck('config', 'Config', 'pass', `No config file yet (${configPath}); default settings are in effect.`, 'Optional: run memesh config list to inspect settings.');
205
194
  }
206
195
  try {
207
196
  const raw = readFileSyncImpl(configPath, 'utf8');
@@ -209,35 +198,66 @@ function inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl,
209
198
  if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
210
199
  return createCheck('config', 'Config', 'fail', `${configPath} parsed but is not a JSON object — every setting is being ignored.`, `Fix or remove ${configPath}, then re-run memesh doctor.`, { code: 'config-parse.not-object', params: { path: configPath } });
211
200
  }
201
+ const retiredKeys = findRetiredConfigKeys(parsed);
202
+ if (retiredKeys.length > 0) {
203
+ const keys = retiredKeys.join(', ');
204
+ return createCheck('config', 'Config', 'warn', `${configPath} is valid JSON but still contains ${retiredKeys.length} retired top-level setting(s) (${keys}). ` +
205
+ 'This version ignores them, but legacy provider credentials or settings may remain on disk.', `Review ${configPath} locally and remove only those retired top-level keys. ` +
206
+ 'Do not paste the file into an issue because it may contain credentials. Then run memesh doctor again.', {
207
+ code: 'config-parse.retired-settings',
208
+ params: { path: configPath, count: retiredKeys.length, keys },
209
+ });
210
+ }
212
211
  return createCheck('config', 'Config', 'pass', `${configPath} is valid JSON and its settings are in effect.`);
213
212
  }
214
213
  catch (err) {
215
214
  const msg = err instanceof Error ? err.message : String(err);
216
- 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 } });
215
+ return createCheck('config', 'Config', 'fail', `${configPath} could not be read or parsed (${msg}). Its settings are being 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
216
  }
218
217
  }
219
- function inspectMcpConfig(packageRoot, existsSyncImpl, readFileSyncImpl) {
220
- const mcpPath = path.join(packageRoot, '.mcp.json');
218
+ const MCP_PLACEHOLDER = '${CLAUDE_PLUGIN_ROOT}';
219
+ function declaredMcpManifest(packageRoot, readFileSyncImpl) {
220
+ const parsed = parseJsonFile(path.join(packageRoot, '.claude-plugin', 'plugin.json'), readFileSyncImpl);
221
+ if (!parsed.ok)
222
+ return null;
223
+ const declared = parsed.value.mcpServers;
224
+ if (typeof declared !== 'string' || !declared.startsWith('./'))
225
+ return null;
226
+ return declared.slice(2);
227
+ }
228
+ function inspectMcpConfig(packageRoot, installChannel, existsSyncImpl, readFileSyncImpl, env) {
229
+ const relativeManifest = declaredMcpManifest(packageRoot, readFileSyncImpl);
230
+ if (relativeManifest === null) {
231
+ 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' });
232
+ }
233
+ const label = relativeManifest;
234
+ const mcpPath = path.join(packageRoot, relativeManifest);
221
235
  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' });
236
+ 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
237
  }
224
238
  const parsed = parseJsonFile(mcpPath, readFileSyncImpl);
225
239
  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 } });
240
+ 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
241
  }
228
242
  const server = parsed.value.mcpServers?.memesh;
229
243
  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' });
244
+ 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
245
  }
232
246
  const args = Array.isArray(server.args) ? server.args : [];
233
247
  const entry = typeof args[0] === 'string' ? args[0] : null;
234
248
  if (entry) {
235
- const resolved = path.resolve(entry.replaceAll('${CLAUDE_PLUGIN_ROOT}', packageRoot));
249
+ const pluginRoot = installChannel === 'plugin-marketplace' ? packageRoot : (env.CLAUDE_PLUGIN_ROOT || null);
250
+ if (entry.includes(MCP_PLACEHOLDER) && pluginRoot === null) {
251
+ 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 } });
252
+ }
253
+ const resolved = pluginRoot === null
254
+ ? path.resolve(packageRoot, entry)
255
+ : path.resolve(entry.replaceAll(MCP_PLACEHOLDER, pluginRoot));
236
256
  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 } });
257
+ 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
258
  }
239
259
  }
240
- return createCheck('mcp-config', 'MCP config', 'pass', '.mcp.json is present, defines the memesh MCP server, and the script it starts exists.');
260
+ return createCheck('mcp-config', 'MCP config', 'pass', `${label} is present, defines the memesh MCP server, and the script it starts exists.`);
241
261
  }
242
262
  function extractHookScriptPaths(hooksConfig, packageRoot) {
243
263
  const hooks = hooksConfig.hooks;
@@ -530,19 +550,11 @@ function defaultResolveShellMemesh() {
530
550
  return null;
531
551
  }
532
552
  }
533
- const RUNTIME_TOO_OLD = 'memesh:node-sqlite-too-old';
534
- function defaultNativeBindingProbe(packageRoot) {
553
+ function defaultNativeBindingProbe(_packageRoot) {
535
554
  try {
536
- const probe = new MemeshDatabase(':memory:', { allowExtension: true });
555
+ const probe = new MemeshDatabase(':memory:');
537
556
  try {
538
- if (typeof probe.enableLoadExtension !== 'function') {
539
- return { ok: false, message: `${RUNTIME_TOO_OLD}: node:sqlite in ${process.version} has no enableLoadExtension` };
540
- }
541
- probe.enableLoadExtension(true);
542
- const localRequire = createRequire(pathToFileURL(path.join(packageRoot, 'package.json')).href);
543
- const sqliteVec = localRequire('sqlite-vec');
544
- sqliteVec.load(probe);
545
- probe.prepare('SELECT vec_version()').get();
557
+ probe.prepare('SELECT 1').get();
546
558
  }
547
559
  finally {
548
560
  probe.close();
@@ -607,19 +619,36 @@ export function hasBuiltInSqlite() {
607
619
  }
608
620
  function inspectNativeBinding(packageRoot, _existsSyncImpl, probeImpl = defaultNativeBindingProbe) {
609
621
  const result = probeImpl(packageRoot);
610
- if (result.ok) {
611
- return createCheck('native-binding', 'SQLite and vector search', 'pass', 'node:sqlite opened a database and sqlite-vec loaded (probe succeeded).');
612
- }
613
- if (result.message.startsWith(RUNTIME_TOO_OLD)) {
614
- return createCheck('native-binding', 'SQLite and vector search', 'fail', `The node:sqlite in this Node (${process.version}) is too old for memesh — it cannot load the vector-search extension. The complete version arrived in Node 22.13.`, 'Upgrade Node to 22.13 or newer, then re-run `memesh doctor`.', { code: 'native-binding.node-too-old', params: { version: process.version } });
622
+ return result.ok
623
+ ? createCheck('native-binding', 'SQLite', 'pass', 'node:sqlite opened a database (probe succeeded).')
624
+ : createCheck('native-binding', 'SQLite', 'fail', `SQLite could not open a database: ${result.message}`, 'Check the Node runtime and reinstall if necessary.', { code: 'native-binding.load-failed', params: { detail: result.message, root: packageRoot } });
625
+ }
626
+ function readVersionFromInstalledBinary(binaryPath, existsSyncImpl, readFileSyncImpl, realpathSyncImpl = fs.realpathSync) {
627
+ let resolved;
628
+ try {
629
+ resolved = realpathSyncImpl(binaryPath);
615
630
  }
616
- const isMissingPackage = /MODULE_NOT_FOUND|Cannot find module/i.test(result.message);
617
- if (isMissingPackage) {
618
- return createCheck('native-binding', 'SQLite and vector search', 'warn', 'sqlite-vec is not installed, so memesh cannot search by meaning. Memories are still saved, and still found by keyword.', 'Run: npm install (in the directory that depends on @pcircle/memesh)', { code: 'native-binding.not-installed' });
631
+ catch {
632
+ resolved = binaryPath;
619
633
  }
620
- 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 } });
634
+ let dir = path.dirname(resolved);
635
+ for (let depth = 0; depth < 8; depth += 1) {
636
+ const pkgPath = path.join(dir, 'package.json');
637
+ if (existsSyncImpl(pkgPath)) {
638
+ const parsed = parseJsonFile(pkgPath, readFileSyncImpl);
639
+ if (!parsed.ok)
640
+ return null;
641
+ const { name, version } = parsed.value;
642
+ return name === '@pcircle/memesh' && typeof version === 'string' ? version : null;
643
+ }
644
+ const parent = path.dirname(dir);
645
+ if (parent === dir)
646
+ return null;
647
+ dir = parent;
648
+ }
649
+ return null;
621
650
  }
622
- function inspectShellCli(installChannel, packageRoot, resolveShellMemeshImpl) {
651
+ function inspectShellCli(installChannel, packageRoot, packageVersion, resolveShellMemeshImpl, existsSyncImpl, readFileSyncImpl) {
623
652
  const shellPath = resolveShellMemeshImpl();
624
653
  const isSameAsCurrent = shellPath ? path.resolve(shellPath).startsWith(path.resolve(packageRoot)) : false;
625
654
  const hasDistinctShellCli = !!shellPath && !isSameAsCurrent;
@@ -629,12 +658,20 @@ function inspectShellCli(installChannel, packageRoot, resolveShellMemeshImpl) {
629
658
  : 'Running from npm-global install — shell access available in this terminal.');
630
659
  }
631
660
  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.`);
661
+ const shellVersion = readVersionFromInstalledBinary(shellPath, existsSyncImpl, readFileSyncImpl);
662
+ if (shellVersion) {
663
+ return {
664
+ ...createInfo('shell-cli', 'Shell CLI on PATH', `Installed versions: this interface ${packageVersion}; terminal ${shellVersion}.`),
665
+ code: 'shell-cli.versions',
666
+ params: { current: packageVersion, terminal: shellVersion, shellPath: shellPath, packageRoot },
667
+ };
668
+ }
669
+ return createCheck('shell-cli', 'Shell CLI on PATH', 'pass', `\`memesh\` resolves to ${shellPath} (separate from this install at ${packageRoot}) — could not read the shell copy's own version to compare.`);
633
670
  }
634
671
  if (installChannel === 'plugin-marketplace') {
635
672
  const host = detectPluginHost(packageRoot) === 'codex' ? 'Codex CLI' : 'Claude Code';
636
673
  return createCheck('shell-cli', 'Shell CLI on PATH', 'warn', 'Plugin is installed but `memesh` is not on the shell PATH. Typing `memesh` in a regular terminal will report `command not found`. '
637
- + `${host} MCP / hooks / \`/memesh\` skill still work — this only affects standalone shell usage and other MCP clients (Cursor, Cline, etc.).`, 'Run `npm install -g @pcircle/memesh` to add the shell CLI. Both paths coexist; they share the same `~/.memesh/knowledge-graph.db`.', { code: 'shell-cli.not-on-path' });
674
+ + `${host} MCP / hooks / \`/memesh\` skill still work — this only affects standalone shell usage and other MCP clients (Cursor, Cline, etc.).`, 'Run `npm install -g @pcircle/memesh` if you want the separate shell CLI. Its database path depends on its environment and configuration.', { code: 'shell-cli.not-on-path' });
638
675
  }
639
676
  return createCheck('shell-cli', 'Shell CLI on PATH', 'pass', shellPath
640
677
  ? `\`memesh\` resolves to ${shellPath}.`
@@ -827,6 +864,24 @@ function inspectPluginCacheCurrency(installChannel, pluginHost, packageRoot, ins
827
864
  }
828
865
  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
866
  }
867
+ function isClaudeChannelCommand(command) {
868
+ if (command === 'memesh-host-claude')
869
+ return true;
870
+ if (typeof command !== 'string' || !path.isAbsolute(command) || path.basename(command) !== 'memesh-host-claude') {
871
+ return false;
872
+ }
873
+ try {
874
+ const target = fs.realpathSync(command);
875
+ const stat = fs.statSync(target);
876
+ if (!stat.isFile())
877
+ return false;
878
+ fs.accessSync(target, fs.constants.X_OK);
879
+ return true;
880
+ }
881
+ catch {
882
+ return false;
883
+ }
884
+ }
830
885
  function inspectClaudeChannelRegistration(existsSyncImpl, readFileSyncImpl) {
831
886
  const configPath = path.join(homeDir(), '.claude.json');
832
887
  let parsed = null;
@@ -875,14 +930,15 @@ function inspectClaudeChannelRegistration(existsSyncImpl, readFileSyncImpl) {
875
930
  targetConfigValid = false;
876
931
  }
877
932
  }
878
- const coherent = command === 'memesh-host-claude'
933
+ const commandValid = isClaudeChannelCommand(command);
934
+ const coherent = commandValid
879
935
  && args !== null
880
936
  && args.length === 2
881
937
  && configIndex === 0
882
938
  && target !== null
883
939
  && targetConfigValid;
884
940
  if (!coherent) {
885
- const reason = command !== 'memesh-host-claude' || args === null || configIndex !== 0 || target === null
941
+ const reason = !commandValid || args === null || configIndex !== 0 || target === null
886
942
  ? 'the command or --config declaration is malformed'
887
943
  : 'the declared owner config target is missing, insecure, malformed, or incomplete';
888
944
  return createCheck('claude-channel', 'Claude Channel registration', 'warn', `The user-scoped memesh-channel registration is present but ${reason}. Live Claude Channel notification is not established.`, 'Remove and re-register the owner-controlled memesh-channel entry with `memesh agent setup claude`; keep its generated config file owner-private.');
@@ -977,7 +1033,13 @@ async function inspectUpdateStatus(packageVersion, getUpdateCheckImpl, installSu
977
1033
  return createCheck('update-status', 'Update status', 'pass', `Running pre-release version (${packageVersion}), npm latest is ${update.latestVersion}`);
978
1034
  }
979
1035
  }
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'
1036
+ const checkedHoursAgo = update.lastSuccessfulCheckAt === null
1037
+ ? null
1038
+ : (Date.now() - Date.parse(update.lastSuccessfulCheckAt)) / 3600_000;
1039
+ const checkedAgo = formatHoursAgo(checkedHoursAgo);
1040
+ return createCheck('update-status', 'Update status', update.freshness === 'stale' ? 'warn' : 'pass', update.freshness === 'stale'
1041
+ ? `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.`
1042
+ : `As of the last check (${checkedAgo}), ${packageVersion} was the latest version.`, update.freshness === 'stale'
981
1043
  ? 'Run `memesh status` while online to refresh cached update metadata.'
982
1044
  : undefined, update.freshness === 'stale'
983
1045
  ? { code: 'update-status.stale', params: { version: packageVersion } }
@@ -1099,8 +1161,7 @@ function inspectMessageCapability(packageRoot, enabled, probe) {
1099
1161
  return createCheck('message-capability', 'Message adapter imports', 'fail', `Installed message capability probe failed: ${result.message}.`, 'Reinstall or rebuild this package, then retry with MEMESH_DOCTOR_PROBE_MESSAGE_CAPABILITY=1.', { code: 'message-capability.probe-failed', params: { detail: result.message } });
1100
1162
  }
1101
1163
  async function defaultMessageRouterStatusProbe() {
1102
- const socketPath = process.env.MEMESH_ROUTER_SOCKET
1103
- ?? path.join(getMemeshDirFromDbPath(), 'agent-router.sock');
1164
+ const socketPath = process.env.MEMESH_ROUTER_SOCKET ?? getAgentRouterSocketPath();
1104
1165
  let stat;
1105
1166
  try {
1106
1167
  stat = fs.lstatSync(socketPath);
@@ -1147,54 +1208,6 @@ async function inspectMessageRouterStatus(enabled, probe) {
1147
1208
  return createCheck('message-router-status', 'Live message router / host registration', 'warn', `An owner-private router socket exists at ${result.socket_path}, but it did not accept a local connection. No host registration or native delivery is verified.`, 'Check the owner-configured `memesh-router` process, then run this opt-in probe again.', { code: 'message-router.socket-unreachable', params: { path: result.socket_path } });
1148
1209
  }
1149
1210
  }
1150
- async function inspectEmbeddingProbe(capabilities, probeCapabilities, embedTextImpl) {
1151
- if (capabilities.embeddings === 'tfidf') {
1152
- return createInfo('embeddings_probe', 'Embeddings work', 'No neural embedder configured — recall runs on FTS5 keyword search alone. That is a supported mode, not a fault.');
1153
- }
1154
- if (!probeCapabilities) {
1155
- return createInfo('embeddings_probe', 'Embeddings work', `NOT VERIFIED. Config names "${capabilities.embeddings}", but generating a test embedding is a network call (billed on hosted providers) so it was not made — a revoked key or an unreachable host would look identical to a healthy setup here.`, 'Run: memesh doctor --probe (generates one test embedding to confirm)');
1156
- }
1157
- let timer;
1158
- try {
1159
- const vector = await Promise.race([
1160
- embedTextImpl('memesh doctor embedding probe'),
1161
- new Promise((_, reject) => {
1162
- timer = setTimeout(() => reject(new Error(`no response within ${EMBEDDING_PROBE_TIMEOUT_MS / 1000}s`)), EMBEDDING_PROBE_TIMEOUT_MS);
1163
- }),
1164
- ]);
1165
- if (!vector || vector.length === 0) {
1166
- return createCheck('embeddings_probe', 'Embeddings work', 'warn', `Config selects "${capabilities.embeddings}" but generating a test embedding returned nothing. Semantic recall is degraded to FTS5-only; keyword search still works.`, 'Run: memesh doctor --probe for detail, or check network access to the embedding provider.', { code: 'embeddings.empty', params: { provider: String(capabilities.embeddings) } });
1167
- }
1168
- return createCheck('embeddings_probe', 'Embeddings work', 'pass', `Generated a ${vector.length}-dim test embedding via "${capabilities.embeddings}".`);
1169
- }
1170
- catch (err) {
1171
- const msg = err instanceof Error ? err.message : String(err);
1172
- return createCheck('embeddings_probe', 'Embeddings work', 'warn', `Config selects "${capabilities.embeddings}" but the embedder threw (${msg}). Semantic recall is degraded to FTS5-only.`, 'Check the embedding provider is reachable (e.g. run `ollama serve`), or remove embedder config to use keyword-only search.', { code: 'embeddings.threw', params: { provider: String(capabilities.embeddings), detail: msg } });
1173
- }
1174
- finally {
1175
- clearTimeout(timer);
1176
- }
1177
- }
1178
- async function inspectLlmProbe(capabilities, probeCapabilities, probeProviderImpl) {
1179
- const llm = capabilities.llm;
1180
- if (!llm) {
1181
- return createInfo('llm_probe', 'LLM reachable', 'No LLM configured — Core Mode. Write-side features (lessons, auto-tag, dream) are off by design.');
1182
- }
1183
- if (!probeCapabilities) {
1184
- return createInfo('llm_probe', 'LLM reachable', `NOT VERIFIED. Config names ${llm.provider} (${llm.model ?? 'default'}), but no live call was made — an expired key or an unreachable host would look identical to a healthy setup here.`, 'Run: memesh doctor --probe (makes one small live call to confirm)');
1185
- }
1186
- try {
1187
- const result = await probeProviderImpl(llm.provider, llm.apiKey);
1188
- if (result.valid) {
1189
- return createCheck('llm_probe', 'LLM reachable', 'pass', `${llm.provider} answered a live probe.`);
1190
- }
1191
- return createCheck('llm_probe', 'LLM reachable', 'fail', `${llm.provider} is configured but did not answer: ${result.error ?? 'unknown error'}. Every LLM-backed feature is silently doing nothing.`, 'Check the API key / host, then re-run: memesh doctor --probe', { code: 'llm.unreachable', params: { provider: llm.provider, detail: result.error ?? 'unknown error' } });
1192
- }
1193
- catch (err) {
1194
- const msg = err instanceof Error ? err.message : String(err);
1195
- return createCheck('llm_probe', 'LLM reachable', 'fail', `${llm.provider} probe threw: ${msg}. Every LLM-backed feature is silently doing nothing.`, 'Check the API key / host, then re-run: memesh doctor --probe', { code: 'llm.threw', params: { provider: llm.provider, detail: msg } });
1196
- }
1197
- }
1198
1211
  function summarizeOverallStatus(checks) {
1199
1212
  const assertions = checks.filter((check) => !check.informational);
1200
1213
  if (assertions.some((check) => check.status === 'fail'))
@@ -1204,7 +1217,7 @@ function summarizeOverallStatus(checks) {
1204
1217
  return 'PASS';
1205
1218
  }
1206
1219
  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;
1220
+ const { packageRoot, packageVersion, probeHttp = false, httpBaseUrl = 'http://127.0.0.1:3737', platform = process.platform, envImpl = process.env, openDatabaseImpl = openDatabase, closeDatabaseImpl = closeDatabase, isDatabaseOpenImpl = isDatabaseOpen, 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
1221
  const wasDbOpenBeforeUs = isDatabaseOpenImpl();
1209
1222
  const safeCloseDatabaseImpl = wasDbOpenBeforeUs
1210
1223
  ? () => undefined
@@ -1240,39 +1253,6 @@ export async function runDoctor(options) {
1240
1253
  `automatic rebuild cannot notice. Re-run doctor after the rebuild: this count should be 0.`, `Run 'memesh reindex --fts' to rebuild the keyword index.`, { code: 'fts.unsegmented', params: { count: unsegmented.c } }, 'fts-rebuild'));
1241
1254
  }
1242
1255
  }
1243
- const pendingReindex = getPendingReindexInfo();
1244
- const vectorDb = db;
1245
- let missingVectors;
1246
- let vectorsPossible = true;
1247
- try {
1248
- vectorsPossible = hasVectorIndex(vectorDb);
1249
- missingVectors = vectorsPossible ? countMissingVectors(vectorDb) : 0;
1250
- }
1251
- catch {
1252
- missingVectors = null;
1253
- }
1254
- const payableDebt = pendingReindex && !(pendingReindex.reason === 'vectors-missing' && !vectorsPossible);
1255
- if (payableDebt || missingVectors === null || missingVectors > 0) {
1256
- const owed = pendingReindex && pendingReindex.reason !== 'vectors-missing'
1257
- ? 'Search index needs rebuilding (embedding configuration changed)'
1258
- : missingVectors === null
1259
- ? 'The vector index could not be read, so how much of your memory semantic recall can see is unknown'
1260
- : `${missingVectors} memor${missingVectors === 1 ? 'y has' : 'ies have'} no search vector, `
1261
- + '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 } }));
1263
- }
1264
- const generation = readVectorGeneration();
1265
- if (generation.state !== 'none') {
1266
- const staged = generationRowIds().size;
1267
- const detail = generation.state === 'open'
1268
- ? `${staged} vectors staged at ${generation.info.dimension} dimensions `
1269
- + `(provider ${generation.info.provider}, started ${generation.info.startedAt})`
1270
- : `${staged} vectors staged, but the marker cannot be read (${generation.detail})`;
1271
- dbChecks.push(createCheck('vector_generation', 'Half-built search index', 'warn', `An unfinished index rebuild is holding disk space: ${detail}.`, generation.state === 'open'
1272
- ? `Run 'memesh reindex' to finish it (the vectors already produced are reused), `
1273
- + `or 'memesh reindex --discard-generation' to reclaim the space.`
1274
- : `Run 'memesh reindex --discard-generation' to clear it, then 'memesh reindex'.`, { code: 'vector-generation.open', params: { staged } }));
1275
- }
1276
1256
  try {
1277
1257
  const guardRows = db
1278
1258
  .prepare(`SELECT id, name, metadata FROM entities
@@ -1406,8 +1386,8 @@ export async function runDoctor(options) {
1406
1386
  catch {
1407
1387
  }
1408
1388
  }
1409
- checks.push(inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl, detectCapabilitiesImpl().llm));
1410
- checks.push(inspectMcpConfig(packageRoot, existsSyncImpl, readFileSyncImpl));
1389
+ checks.push(inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl));
1390
+ checks.push(inspectMcpConfig(packageRoot, install, existsSyncImpl, readFileSyncImpl, envImpl));
1411
1391
  checks.push(...inspectHooksConfig(packageRoot, platform, existsSyncImpl, readFileSyncImpl, statSyncImpl));
1412
1392
  const pluginHost = detectPluginHost(packageRoot);
1413
1393
  let codexPluginCacheDetected = pluginHost === 'codex';
@@ -1450,24 +1430,10 @@ export async function runDoctor(options) {
1450
1430
  checks.push(inspectDashboardArtifact(packageRoot, existsSyncImpl));
1451
1431
  checks.push(inspectNodeRuntime(packageRoot, existsSyncImpl, readFileSyncImpl));
1452
1432
  checks.push(inspectNativeBinding(packageRoot, existsSyncImpl, nativeBindingProbeImpl));
1453
- checks.push(inspectShellCli(install, packageRoot, resolveShellMemeshImpl));
1433
+ checks.push(inspectShellCli(install, packageRoot, packageVersion, resolveShellMemeshImpl, existsSyncImpl, readFileSyncImpl));
1454
1434
  checks.push(verifySkillsManifest(packageRoot, existsSyncImpl, readFileSyncImpl, installSupport));
1455
1435
  checks.push(inspectMessageCapability(packageRoot, probeMessageCapability, messageCapabilityProbeImpl));
1456
1436
  checks.push(await inspectMessageRouterStatus(probeMessageRouterStatus, messageRouterStatusProbeImpl));
1457
- const capabilities = detectCapabilitiesImpl();
1458
- checks.push(createInfo('capabilities', 'Capabilities (configured)', `Search level ${capabilities.searchLevel} (${capabilities.searchLevel === 1 ? 'Smart Mode' : 'Core'}); embeddings: ${capabilities.embeddings}; LLM: ${capabilities.llm ? `${capabilities.llm.provider} (${capabilities.llm.model ?? 'default'})` : 'not configured'}. Configured values only — see the probe rows below for what actually works.`));
1459
- if (!isTranscriptMiningEnabled()) {
1460
- checks.push(createInfo('transcript-mining', 'Scheduled transcript mining', 'Off (opt-in). memesh can mine this project\'s Claude Code session transcripts for decisions and lessons and STAGE them for your review. Turn it on with `memesh config set transcriptMining true`, then have a scheduler (cron/launchd) run `memesh dream run --from-transcripts --if-due` — it self-throttles and stages only, so nothing enters your graph without `dream accept`.'));
1461
- }
1462
- else {
1463
- const last = lastTranscriptMineAt(getProjectName(process.cwd()));
1464
- const when = last === null
1465
- ? 'not yet run for this project'
1466
- : `last mined ${((Date.now() - last) / 3600_000).toFixed(1)}h ago`;
1467
- checks.push(createInfo('transcript-mining', 'Scheduled transcript mining', `On for this project — ${when}. Have a scheduler run \`memesh dream run --from-transcripts --if-due\`; it mines when due (default every 24h) and stages proposals. Review the queue with \`memesh dream list\`.`));
1468
- }
1469
- checks.push(await inspectEmbeddingProbe(capabilities, probeCapabilities, embedTextImpl));
1470
- checks.push(await inspectLlmProbe(capabilities, probeCapabilities, probeProviderImpl));
1471
1437
  checks.push(await inspectUpdateStatus(packageVersion, getUpdateCheckImpl, installSupport));
1472
1438
  try {
1473
1439
  const record = getInstallRecord();