@pcircle/memesh 4.8.2 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/.codex-plugin/mcp.json +7 -0
- package/.codex-plugin/plugin.json +6 -0
- package/AGENTS.md +18 -8
- package/README.de.md +13 -11
- package/README.md +25 -12
- package/README.zh-TW.md +25 -12
- package/dashboard/dist/index.html +3 -3
- package/dist/core/agent-message-inbox.d.ts +3 -2
- package/dist/core/agent-message-inbox.d.ts.map +1 -1
- package/dist/core/agent-message-inbox.js +30 -6
- package/dist/core/agent-message-inbox.js.map +1 -1
- package/dist/core/agent-messaging.d.ts +7 -0
- package/dist/core/agent-messaging.d.ts.map +1 -1
- package/dist/core/agent-messaging.js +51 -24
- package/dist/core/agent-messaging.js.map +1 -1
- package/dist/core/agent-router.d.ts +27 -20
- package/dist/core/agent-router.d.ts.map +1 -1
- package/dist/core/agent-router.js +114 -43
- package/dist/core/agent-router.js.map +1 -1
- package/dist/core/agent-scope-id.d.ts +11 -0
- package/dist/core/agent-scope-id.d.ts.map +1 -0
- package/dist/core/agent-scope-id.js +40 -0
- package/dist/core/agent-scope-id.js.map +1 -0
- package/dist/core/briefing.d.ts +1 -1
- package/dist/core/briefing.d.ts.map +1 -1
- package/dist/core/briefing.js +25 -7
- package/dist/core/briefing.js.map +1 -1
- package/dist/core/citation-rule.d.ts.map +1 -1
- package/dist/core/citation-rule.js +5 -2
- package/dist/core/citation-rule.js.map +1 -1
- package/dist/core/doctor.d.ts +9 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +461 -24
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +3 -1
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +4 -1
- package/dist/core/embedder.js.map +1 -1
- package/dist/core/install-channel.d.ts +3 -0
- package/dist/core/install-channel.d.ts.map +1 -1
- package/dist/core/install-channel.js +79 -2
- package/dist/core/install-channel.js.map +1 -1
- package/dist/core/install-hooks.d.ts.map +1 -1
- package/dist/core/install-hooks.js +10 -6
- package/dist/core/install-hooks.js.map +1 -1
- package/dist/core/lesson-slug.d.ts.map +1 -1
- package/dist/core/lesson-slug.js +6 -4
- package/dist/core/lesson-slug.js.map +1 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +8 -5
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core/llm-client.d.ts.map +1 -1
- package/dist/core/llm-client.js +4 -1
- package/dist/core/llm-client.js.map +1 -1
- package/dist/core/llm-validator.d.ts.map +1 -1
- package/dist/core/llm-validator.js +6 -15
- package/dist/core/llm-validator.js.map +1 -1
- package/dist/core/memory-tool.d.ts.map +1 -1
- package/dist/core/memory-tool.js +3 -1
- package/dist/core/memory-tool.js.map +1 -1
- package/dist/core/ollama-host.d.ts +6 -0
- package/dist/core/ollama-host.d.ts.map +1 -0
- package/dist/core/ollama-host.js +62 -0
- package/dist/core/ollama-host.js.map +1 -0
- package/dist/core/operations.d.ts +1 -1
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +1 -1
- package/dist/core/operations.js.map +1 -1
- package/dist/core/project-tags.d.ts +2 -0
- package/dist/core/project-tags.d.ts.map +1 -1
- package/dist/core/project-tags.js +29 -1
- package/dist/core/project-tags.js.map +1 -1
- package/dist/core/schema-export.d.ts.map +1 -1
- package/dist/core/schema-export.js +10 -8
- package/dist/core/schema-export.js.map +1 -1
- package/dist/core/version-check.d.ts +1 -0
- package/dist/core/version-check.d.ts.map +1 -1
- package/dist/core/version-check.js +35 -0
- package/dist/core/version-check.js.map +1 -1
- package/dist/core/work-topology.d.ts +3 -0
- package/dist/core/work-topology.d.ts.map +1 -1
- package/dist/core/work-topology.js +24 -2
- package/dist/core/work-topology.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +5 -2
- package/dist/db.js.map +1 -1
- package/dist/host-adapters/claude-channel.d.ts +0 -58
- package/dist/host-adapters/claude-channel.d.ts.map +1 -1
- package/dist/host-adapters/claude-channel.js +0 -242
- package/dist/host-adapters/claude-channel.js.map +1 -1
- package/dist/host-adapters/codex-cli-queue.d.ts.map +1 -1
- package/dist/host-adapters/codex-cli-queue.js +15 -12
- package/dist/host-adapters/codex-cli-queue.js.map +1 -1
- package/dist/host-runtime/acp.d.ts +2 -0
- package/dist/host-runtime/acp.d.ts.map +1 -1
- package/dist/host-runtime/acp.js +6 -1
- package/dist/host-runtime/acp.js.map +1 -1
- package/dist/host-runtime/claude.d.ts +2 -0
- package/dist/host-runtime/claude.d.ts.map +1 -1
- package/dist/host-runtime/claude.js +12 -13
- package/dist/host-runtime/claude.js.map +1 -1
- package/dist/host-runtime/codex-session.d.ts +3 -1
- package/dist/host-runtime/codex-session.d.ts.map +1 -1
- package/dist/host-runtime/codex-session.js +7 -5
- package/dist/host-runtime/codex-session.js.map +1 -1
- package/dist/host-runtime/codex.d.ts +2 -0
- package/dist/host-runtime/codex.d.ts.map +1 -1
- package/dist/host-runtime/codex.js +6 -1
- package/dist/host-runtime/codex.js.map +1 -1
- package/dist/host-runtime/entry.d.ts +5 -0
- package/dist/host-runtime/entry.d.ts.map +1 -0
- package/dist/host-runtime/entry.js +11 -0
- package/dist/host-runtime/entry.js.map +1 -0
- package/dist/host-runtime/router-client.d.ts +2 -0
- package/dist/host-runtime/router-client.d.ts.map +1 -1
- package/dist/host-runtime/router-client.js.map +1 -1
- package/dist/knowledge-graph.d.ts +0 -1
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +27 -19
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/skills-manifest.json +40 -25
- package/dist/storage/entity-index.d.ts +4 -0
- package/dist/storage/entity-index.d.ts.map +1 -0
- package/dist/storage/entity-index.js +15 -0
- package/dist/storage/entity-index.js.map +1 -0
- package/dist/storage/fts-index.d.ts.map +1 -1
- package/dist/storage/fts-index.js +5 -0
- package/dist/storage/fts-index.js.map +1 -1
- package/dist/storage/graph-repairs.d.ts +11 -1
- package/dist/storage/graph-repairs.d.ts.map +1 -1
- package/dist/storage/graph-repairs.js +241 -30
- package/dist/storage/graph-repairs.js.map +1 -1
- package/dist/transports/agent-messaging.d.ts +10 -1
- package/dist/transports/agent-messaging.d.ts.map +1 -1
- package/dist/transports/agent-messaging.js +92 -9
- package/dist/transports/agent-messaging.js.map +1 -1
- package/dist/transports/cli/cli.d.ts +4 -0
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +88 -38
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +12 -8
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +23 -11
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/dist/transports/schemas.d.ts +22 -17
- package/dist/transports/schemas.d.ts.map +1 -1
- package/dist/transports/schemas.js +25 -11
- package/dist/transports/schemas.js.map +1 -1
- package/docs/platforms/README.md +2 -2
- package/docs/platforms/agent-messaging.md +246 -36
- package/hooks/hooks.json +10 -0
- package/llms-install.md +29 -6
- package/package.json +13 -5
- package/scripts/hooks/_generated/agent-message-inbox.js +30 -6
- package/scripts/hooks/_generated/citation-rule.js +5 -2
- package/scripts/hooks/_generated/fts-index.js +5 -0
- package/scripts/hooks/_generated/work-topology.js +24 -2
- package/scripts/hooks/_shared.js +52 -4
- package/scripts/hooks/decision-nudge.js +152 -0
- package/scripts/hooks/post-commit.js +11 -0
- package/scripts/hooks/pre-compact.js +12 -4
- package/scripts/hooks/session-start.js +61 -26
- package/scripts/upgrade-plugin.sh +600 -81
- package/skills/memesh/SKILL.md +24 -5
- /package/{.mcp.json → .claude-plugin/mcp.json} +0 -0
package/dist/core/doctor.js
CHANGED
|
@@ -12,7 +12,7 @@ import { probeProvider } from './llm-validator.js';
|
|
|
12
12
|
import { openDatabase, closeDatabase, getPendingReindexInfo, isDatabaseOpen, readVectorGeneration, generationRowIds, } from '../db.js';
|
|
13
13
|
import { getUpdateCheck } from './version-check.js';
|
|
14
14
|
import { classifyBump } from './updater.js';
|
|
15
|
-
import { getCurrentInstallChannel, getInstallChannelSupport, detectPluginHost } from './install-channel.js';
|
|
15
|
+
import { getCurrentInstallChannel, getInstallChannelSupport, detectPluginHost, pluginHostConfigRoot, versionedPluginCacheRoots, PLUGIN_REFRESH_COMMANDS, } from './install-channel.js';
|
|
16
16
|
import { getInstallRecord } from './install-id.js';
|
|
17
17
|
import { citationRulePath, citationRuleState } from './citation-rule.js';
|
|
18
18
|
import { getDbPath, getMemeshDirFromDbPath, homeDir, memeshDir, getProjectName } from './paths.js';
|
|
@@ -27,6 +27,8 @@ import { parseSqliteUtcMs } from './time-utils.js';
|
|
|
27
27
|
import { autoCaptureDecision } from './capture-flag.js';
|
|
28
28
|
import { guardFromMetadata } from './guards.js';
|
|
29
29
|
import { getAgentMessageStorageReport } from './agent-message-storage.js';
|
|
30
|
+
import { readHostConfigFile } from '../host-runtime/config.js';
|
|
31
|
+
import { summariseTelemetry } from './llm-telemetry.js';
|
|
30
32
|
const EMBEDDING_PROBE_TIMEOUT_MS = 15000;
|
|
31
33
|
const EXPECTED_HOOK_TYPES = ['PreToolUse', 'SessionStart', 'PostToolUse', 'Stop', 'PreCompact'];
|
|
32
34
|
const AGENT_MESSAGE_STORAGE_QUOTA_ENV = 'MEMESH_AGENT_MESSAGE_STORAGE_QUOTA_BYTES';
|
|
@@ -147,6 +149,15 @@ function inspectAgentMessageStorage(db, databasePath, policy) {
|
|
|
147
149
|
return undefined;
|
|
148
150
|
}
|
|
149
151
|
}
|
|
152
|
+
function inspectCodexSessionSetup(codexPluginCacheDetected, existsSyncImpl) {
|
|
153
|
+
if (!codexPluginCacheDetected)
|
|
154
|
+
return null;
|
|
155
|
+
const configPath = path.join(getMemeshDirFromDbPath(), 'hosts', 'codex-session.json');
|
|
156
|
+
if (existsSyncImpl(configPath)) {
|
|
157
|
+
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.');
|
|
158
|
+
}
|
|
159
|
+
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' });
|
|
160
|
+
}
|
|
150
161
|
function configuredAgentMessageStoragePolicy(explicit) {
|
|
151
162
|
if (explicit !== undefined)
|
|
152
163
|
return explicit;
|
|
@@ -206,28 +217,49 @@ function inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl,
|
|
|
206
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 } });
|
|
207
218
|
}
|
|
208
219
|
}
|
|
209
|
-
|
|
210
|
-
|
|
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);
|
|
211
237
|
if (!existsSyncImpl(mcpPath)) {
|
|
212
|
-
return createCheck('mcp-config', 'MCP config', 'fail',
|
|
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' });
|
|
213
239
|
}
|
|
214
240
|
const parsed = parseJsonFile(mcpPath, readFileSyncImpl);
|
|
215
241
|
if (!parsed.ok) {
|
|
216
|
-
return createCheck('mcp-config', 'MCP config', 'fail',
|
|
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 } });
|
|
217
243
|
}
|
|
218
244
|
const server = parsed.value.mcpServers?.memesh;
|
|
219
245
|
if (!server || typeof server.command !== 'string') {
|
|
220
|
-
return createCheck('mcp-config', 'MCP config', 'fail',
|
|
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' });
|
|
221
247
|
}
|
|
222
248
|
const args = Array.isArray(server.args) ? server.args : [];
|
|
223
249
|
const entry = typeof args[0] === 'string' ? args[0] : null;
|
|
224
250
|
if (entry) {
|
|
225
|
-
const
|
|
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));
|
|
226
258
|
if (!existsSyncImpl(resolved)) {
|
|
227
|
-
return createCheck('mcp-config', 'MCP config', 'fail',
|
|
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 } });
|
|
228
260
|
}
|
|
229
261
|
}
|
|
230
|
-
return createCheck('mcp-config', 'MCP config', 'pass',
|
|
262
|
+
return createCheck('mcp-config', 'MCP config', 'pass', `${label} is present, defines the memesh MCP server, and the script it starts exists.`);
|
|
231
263
|
}
|
|
232
264
|
function extractHookScriptPaths(hooksConfig, packageRoot) {
|
|
233
265
|
const hooks = hooksConfig.hooks;
|
|
@@ -235,8 +267,12 @@ function extractHookScriptPaths(hooksConfig, packageRoot) {
|
|
|
235
267
|
return [];
|
|
236
268
|
const scripts = new Set();
|
|
237
269
|
for (const entries of Object.values(hooks)) {
|
|
238
|
-
|
|
239
|
-
|
|
270
|
+
if (!Array.isArray(entries))
|
|
271
|
+
continue;
|
|
272
|
+
for (const entry of entries) {
|
|
273
|
+
if (!Array.isArray(entry.hooks))
|
|
274
|
+
continue;
|
|
275
|
+
for (const hook of entry.hooks) {
|
|
240
276
|
if (typeof hook.command !== 'string')
|
|
241
277
|
continue;
|
|
242
278
|
const command = hook.command.replace('${CLAUDE_PLUGIN_ROOT}/', '');
|
|
@@ -259,7 +295,10 @@ function inspectHooksConfig(packageRoot, platform, existsSyncImpl, readFileSyncI
|
|
|
259
295
|
createCheck('hooks-config', 'Hooks config', 'fail', 'hooks/hooks.json is not valid JSON.', `Fix ${hooksPath} so Claude Code can load the hook definitions.`, { code: 'hooks-config.invalid-json', params: { path: hooksPath } }),
|
|
260
296
|
];
|
|
261
297
|
}
|
|
262
|
-
const
|
|
298
|
+
const parsedHooks = parsed.value.hooks;
|
|
299
|
+
const hookTypes = parsedHooks && typeof parsedHooks === 'object' && !Array.isArray(parsedHooks)
|
|
300
|
+
? Object.keys(parsedHooks)
|
|
301
|
+
: [];
|
|
263
302
|
const missingTypes = EXPECTED_HOOK_TYPES.filter((type) => !hookTypes.includes(type));
|
|
264
303
|
const configCheck = missingTypes.length > 0
|
|
265
304
|
? createCheck('hooks-config', 'Hooks config', 'fail', `hooks/hooks.json is missing expected hook types: ${missingTypes.join(', ')}.`, 'Restore the shipped hook configuration or reinstall MeMesh.', { code: 'hooks-config.missing-types', params: { types: missingTypes.join(', ') } })
|
|
@@ -602,7 +641,32 @@ function inspectNativeBinding(packageRoot, _existsSyncImpl, probeImpl = defaultN
|
|
|
602
641
|
}
|
|
603
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 } });
|
|
604
643
|
}
|
|
605
|
-
function
|
|
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) {
|
|
606
670
|
const shellPath = resolveShellMemeshImpl();
|
|
607
671
|
const isSameAsCurrent = shellPath ? path.resolve(shellPath).startsWith(path.resolve(packageRoot)) : false;
|
|
608
672
|
const hasDistinctShellCli = !!shellPath && !isSameAsCurrent;
|
|
@@ -612,7 +676,25 @@ function inspectShellCli(installChannel, packageRoot, resolveShellMemeshImpl) {
|
|
|
612
676
|
: 'Running from npm-global install — shell access available in this terminal.');
|
|
613
677
|
}
|
|
614
678
|
if (hasDistinctShellCli) {
|
|
615
|
-
|
|
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.'));
|
|
616
698
|
}
|
|
617
699
|
if (installChannel === 'plugin-marketplace') {
|
|
618
700
|
const host = detectPluginHost(packageRoot) === 'codex' ? 'Codex CLI' : 'Claude Code';
|
|
@@ -623,6 +705,282 @@ function inspectShellCli(installChannel, packageRoot, resolveShellMemeshImpl) {
|
|
|
623
705
|
? `\`memesh\` resolves to ${shellPath}.`
|
|
624
706
|
: `No shell-PATH \`memesh\` detected. If you want terminal access, run \`npm install -g @pcircle/memesh\` (this install is a ${installChannel}, so the check is informational only).`);
|
|
625
707
|
}
|
|
708
|
+
function defaultMarketplaceHeadSha(host) {
|
|
709
|
+
if (host === 'codex') {
|
|
710
|
+
const codexHome = pluginHostConfigRoot('codex');
|
|
711
|
+
return readCodexInstallRevision(path.join(codexHome, '.tmp', 'marketplaces', 'pcircle-memesh'), fs.readFileSync);
|
|
712
|
+
}
|
|
713
|
+
const dir = path.join(pluginHostConfigRoot('claude-code'), 'plugins', 'marketplaces', 'pcircle-memesh');
|
|
714
|
+
try {
|
|
715
|
+
const out = execFileSync('git', ['-C', dir, 'rev-parse', 'HEAD'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
716
|
+
return /^[0-9a-f]{40}$/.test(out) ? out : null;
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function readCodexInstallRevision(root, readFileSyncImpl) {
|
|
723
|
+
const parsed = parseJsonFile(path.join(root, '.codex-marketplace-install.json'), readFileSyncImpl);
|
|
724
|
+
if (!parsed.ok)
|
|
725
|
+
return null;
|
|
726
|
+
const rev = parsed.value.revision;
|
|
727
|
+
return typeof rev === 'string' && /^[0-9a-f]{40}$/.test(rev) ? rev : null;
|
|
728
|
+
}
|
|
729
|
+
function pluginCacheUnverifiable(host, missing) {
|
|
730
|
+
const hostLabel = host === 'codex' ? 'Codex' : 'Claude Code';
|
|
731
|
+
const command = PLUGIN_REFRESH_COMMANDS[host];
|
|
732
|
+
return createCheck('plugin-cache', `Plugin cache source record is current (${hostLabel})`, 'warn', `Could not tell whether the plugin cache matches the marketplace: ${missing}. The version alone cannot answer this — two builds can carry the same version.`, `Run \`${command}\` — it re-stages the cache from the marketplace and records the commit, after which this check can answer.`, { code: 'plugin-cache.unverifiable', params: { host: hostLabel, command } });
|
|
733
|
+
}
|
|
734
|
+
function readClaudePluginEntries(registryPath, readFileSyncImpl, existsSyncImpl) {
|
|
735
|
+
if (!existsSyncImpl(registryPath)) {
|
|
736
|
+
return { exists: false, readable: false, defined: false, malformed: false, entries: [] };
|
|
737
|
+
}
|
|
738
|
+
const parsed = parseJsonFile(registryPath, readFileSyncImpl);
|
|
739
|
+
if (!parsed.ok)
|
|
740
|
+
return { exists: true, readable: false, defined: false, malformed: false, entries: [] };
|
|
741
|
+
const raw = parsed.value?.plugins?.['memesh@pcircle-memesh'];
|
|
742
|
+
const entries = Array.isArray(raw)
|
|
743
|
+
? raw.filter((entry) => !!entry && typeof entry === 'object')
|
|
744
|
+
: [];
|
|
745
|
+
return {
|
|
746
|
+
exists: true,
|
|
747
|
+
readable: true,
|
|
748
|
+
defined: raw !== undefined,
|
|
749
|
+
malformed: raw !== undefined && (!Array.isArray(raw) || entries.length !== raw.length),
|
|
750
|
+
entries,
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function defaultPluginCacheDiscovery(readFileSyncImpl, existsSyncImpl) {
|
|
754
|
+
const discovered = [];
|
|
755
|
+
const claudeConfigRoot = pluginHostConfigRoot('claude-code');
|
|
756
|
+
const claudeRegistry = path.join(claudeConfigRoot, 'plugins', 'installed_plugins.json');
|
|
757
|
+
const claudeCacheRoot = path.join(claudeConfigRoot, 'plugins', 'cache', 'pcircle-memesh', 'memesh');
|
|
758
|
+
const registry = readClaudePluginEntries(claudeRegistry, readFileSyncImpl, existsSyncImpl);
|
|
759
|
+
const entries = registry.entries;
|
|
760
|
+
if (registry.malformed) {
|
|
761
|
+
discovered.push({
|
|
762
|
+
host: 'claude-code',
|
|
763
|
+
packageRoot: claudeCacheRoot,
|
|
764
|
+
installedPluginsPath: claudeRegistry,
|
|
765
|
+
unverifiableReason: 'installed_plugins.json has a malformed memesh entry',
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
else if (entries.length > 0) {
|
|
769
|
+
const cachesByRoot = new Map();
|
|
770
|
+
for (const entry of entries) {
|
|
771
|
+
const recordedInstallPath = typeof entry.installPath === 'string' && entry.installPath.length > 0
|
|
772
|
+
? entry.installPath
|
|
773
|
+
: null;
|
|
774
|
+
const installPath = recordedInstallPath ?? claudeCacheRoot;
|
|
775
|
+
const cache = {
|
|
776
|
+
host: 'claude-code', packageRoot: installPath, installedPluginsPath: claudeRegistry,
|
|
777
|
+
...(!recordedInstallPath ? { unverifiableReason: 'an installed_plugins.json entry has no usable installPath' }
|
|
778
|
+
: !existsSyncImpl(installPath) ? { unverifiableReason: `the recorded plugin cache does not exist at ${installPath}` } : {}),
|
|
779
|
+
};
|
|
780
|
+
const rootKey = path.resolve(installPath);
|
|
781
|
+
const existing = cachesByRoot.get(rootKey);
|
|
782
|
+
if (!existing)
|
|
783
|
+
cachesByRoot.set(rootKey, cache);
|
|
784
|
+
else if (!existing.unverifiableReason && cache.unverifiableReason) {
|
|
785
|
+
existing.unverifiableReason = cache.unverifiableReason;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
discovered.push(...cachesByRoot.values());
|
|
789
|
+
}
|
|
790
|
+
else if (registry.defined) {
|
|
791
|
+
const cachedRoots = versionedPluginCacheRoots(claudeCacheRoot);
|
|
792
|
+
if (cachedRoots.length > 0) {
|
|
793
|
+
discovered.push({
|
|
794
|
+
host: 'claude-code',
|
|
795
|
+
packageRoot: cachedRoots[cachedRoots.length - 1],
|
|
796
|
+
installedPluginsPath: claudeRegistry,
|
|
797
|
+
unverifiableReason: 'installed_plugins.json records no active memesh install while a versioned plugin cache still exists',
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
else if (!registry.readable) {
|
|
802
|
+
const cachedRoots = versionedPluginCacheRoots(claudeCacheRoot);
|
|
803
|
+
const cachedRoot = cachedRoots[cachedRoots.length - 1];
|
|
804
|
+
if (cachedRoot) {
|
|
805
|
+
discovered.push({
|
|
806
|
+
host: 'claude-code', packageRoot: cachedRoot, installedPluginsPath: claudeRegistry,
|
|
807
|
+
unverifiableReason: registry.exists
|
|
808
|
+
? 'installed_plugins.json could not be read or parsed'
|
|
809
|
+
: 'installed_plugins.json does not exist while a versioned plugin cache still exists',
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
const codexHome = pluginHostConfigRoot('codex');
|
|
814
|
+
const codexCacheRoot = path.join(codexHome, 'plugins', 'cache', 'pcircle-memesh', 'memesh');
|
|
815
|
+
const codexRoots = versionedPluginCacheRoots(codexCacheRoot);
|
|
816
|
+
if (codexRoots.length === 1) {
|
|
817
|
+
discovered.push({ host: 'codex', packageRoot: codexRoots[0] });
|
|
818
|
+
}
|
|
819
|
+
else if (codexRoots.length > 1) {
|
|
820
|
+
discovered.push({
|
|
821
|
+
host: 'codex',
|
|
822
|
+
packageRoot: codexRoots[codexRoots.length - 1],
|
|
823
|
+
unverifiableReason: `several versioned Codex plugin cache directories exist under ${codexCacheRoot}`,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
return discovered;
|
|
827
|
+
}
|
|
828
|
+
function inspectPluginCacheCurrency(installChannel, pluginHost, packageRoot, installedPluginsPath, readFileSyncImpl, existsSyncImpl, marketplaceHeadShaImpl) {
|
|
829
|
+
if (installChannel !== 'plugin-marketplace')
|
|
830
|
+
return null;
|
|
831
|
+
const host = pluginHost === 'codex' ? 'codex' : 'claude-code';
|
|
832
|
+
const hostLabel = host === 'codex' ? 'Codex' : 'Claude Code';
|
|
833
|
+
const command = PLUGIN_REFRESH_COMMANDS[host];
|
|
834
|
+
let installedSha;
|
|
835
|
+
let installedMissing;
|
|
836
|
+
if (host === 'codex') {
|
|
837
|
+
installedSha = readCodexInstallRevision(packageRoot, readFileSyncImpl);
|
|
838
|
+
installedMissing = 'the plugin cache carries no readable .codex-marketplace-install.json revision';
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
const registryPath = installedPluginsPath ?? path.join(pluginHostConfigRoot('claude-code'), 'plugins', 'installed_plugins.json');
|
|
842
|
+
const registry = readClaudePluginEntries(registryPath, readFileSyncImpl, existsSyncImpl);
|
|
843
|
+
const entries = registry.entries;
|
|
844
|
+
const here = path.resolve(packageRoot);
|
|
845
|
+
const matching = entries.filter(e => typeof e?.installPath === 'string' && path.resolve(e.installPath) === here);
|
|
846
|
+
const soleEntry = entries.length === 1 ? entries[0] : undefined;
|
|
847
|
+
const soleEntryHasPath = typeof soleEntry?.installPath === 'string' && soleEntry.installPath.length > 0;
|
|
848
|
+
const entry = matching.length === 1 ? matching[0] : undefined;
|
|
849
|
+
installedSha = !registry.malformed
|
|
850
|
+
&& typeof entry?.gitCommitSha === 'string'
|
|
851
|
+
&& /^[0-9a-f]{40}$/.test(entry.gitCommitSha)
|
|
852
|
+
? entry.gitCommitSha
|
|
853
|
+
: null;
|
|
854
|
+
if (!registry.exists) {
|
|
855
|
+
installedMissing = `installed_plugins.json does not exist at ${registryPath}`;
|
|
856
|
+
}
|
|
857
|
+
else if (!registry.readable) {
|
|
858
|
+
installedMissing = 'installed_plugins.json could not be read or parsed';
|
|
859
|
+
}
|
|
860
|
+
else if (registry.malformed) {
|
|
861
|
+
installedMissing = 'installed_plugins.json has a malformed memesh entry';
|
|
862
|
+
}
|
|
863
|
+
else if (!registry.defined) {
|
|
864
|
+
installedMissing = 'installed_plugins.json has no memesh@pcircle-memesh entry';
|
|
865
|
+
}
|
|
866
|
+
else if (entries.length === 0) {
|
|
867
|
+
installedMissing = 'installed_plugins.json records no active memesh install for this cache';
|
|
868
|
+
}
|
|
869
|
+
else if (matching.length > 1) {
|
|
870
|
+
installedMissing = `installed_plugins.json lists ${matching.length} memesh entries for this install (${packageRoot})`;
|
|
871
|
+
}
|
|
872
|
+
else if (matching.length === 0 && soleEntryHasPath) {
|
|
873
|
+
installedMissing = `the only memesh entry in installed_plugins.json names another install, not this one (${packageRoot})`;
|
|
874
|
+
}
|
|
875
|
+
else if (matching.length === 0 && soleEntry) {
|
|
876
|
+
installedMissing = 'the only memesh entry in installed_plugins.json has no usable installPath to identify this cache';
|
|
877
|
+
}
|
|
878
|
+
else if (entries.length > 1 && matching.length === 0) {
|
|
879
|
+
installedMissing = `installed_plugins.json lists ${entries.length} memesh entries and none of them is this install (${packageRoot})`;
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
installedMissing = 'installed_plugins.json does not record the commit this plugin was installed from';
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
const marketplaceSha = marketplaceHeadShaImpl(host);
|
|
886
|
+
if (!installedSha || !marketplaceSha) {
|
|
887
|
+
const missing = !installedSha ? installedMissing : `the ${hostLabel} marketplace snapshot has no readable commit`;
|
|
888
|
+
return pluginCacheUnverifiable(host, missing);
|
|
889
|
+
}
|
|
890
|
+
if (installedSha === marketplaceSha) {
|
|
891
|
+
return createCheck('plugin-cache', `Plugin cache source record is current (${hostLabel})`, 'pass', `The plugin cache records marketplace commit ${marketplaceSha.slice(0, 8)}, which matches the current marketplace snapshot.`);
|
|
892
|
+
}
|
|
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 } });
|
|
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
|
+
}
|
|
922
|
+
function inspectClaudeChannelRegistration(existsSyncImpl, readFileSyncImpl) {
|
|
923
|
+
const configPath = path.join(homeDir(), '.claude.json');
|
|
924
|
+
let parsed = null;
|
|
925
|
+
if (existsSyncImpl(configPath)) {
|
|
926
|
+
try {
|
|
927
|
+
const value = JSON.parse(readFileSyncImpl(configPath, 'utf8'));
|
|
928
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
929
|
+
throw new Error('not an object');
|
|
930
|
+
parsed = value;
|
|
931
|
+
}
|
|
932
|
+
catch {
|
|
933
|
+
return createCheck('claude-channel', 'Claude Channel registration', 'warn', 'The canonical Claude user config could not be read as JSON, so the memesh-channel registration is malformed or unverifiable.', 'Repair the owner-controlled Claude config with `claude mcp remove memesh-channel` and re-run `memesh agent setup claude` to obtain a fresh registration command.');
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const servers = parsed?.mcpServers;
|
|
937
|
+
const server = servers && typeof servers === 'object' && !Array.isArray(servers)
|
|
938
|
+
? servers['memesh-channel']
|
|
939
|
+
: undefined;
|
|
940
|
+
if (server === undefined) {
|
|
941
|
+
return createCheck('claude-channel', 'Claude Channel registration', 'warn', 'No user-scoped memesh-channel registration was found. Durable MCP/inbox messaging can still work, but live Claude Channel notification is inactive. The upstream research-preview channel remains opt-in.', 'If you want the opt-in channel, run `memesh agent setup claude` and then register the printed user-scoped MCP command.');
|
|
942
|
+
}
|
|
943
|
+
const record = server && typeof server === 'object' && !Array.isArray(server)
|
|
944
|
+
? server
|
|
945
|
+
: null;
|
|
946
|
+
const command = record?.command;
|
|
947
|
+
const rawArgs = record?.args;
|
|
948
|
+
const args = Array.isArray(rawArgs) ? rawArgs : null;
|
|
949
|
+
const configIndex = args?.indexOf('--config') ?? -1;
|
|
950
|
+
const target = args && configIndex >= 0 && typeof args[configIndex + 1] === 'string'
|
|
951
|
+
? args[configIndex + 1]
|
|
952
|
+
: null;
|
|
953
|
+
let targetConfigValid = false;
|
|
954
|
+
if (target) {
|
|
955
|
+
try {
|
|
956
|
+
const config = readHostConfigFile(target);
|
|
957
|
+
const required = ['router_socket', 'token_file', 'project', 'principal_id'];
|
|
958
|
+
targetConfigValid = config.server_name === 'memesh-channel'
|
|
959
|
+
&& required.every((key) => {
|
|
960
|
+
const value = config[key];
|
|
961
|
+
return typeof value === 'string'
|
|
962
|
+
&& value.length > 0
|
|
963
|
+
&& Buffer.byteLength(value) <= 4096;
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
catch {
|
|
967
|
+
targetConfigValid = false;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
const coherent = command === 'memesh-host-claude'
|
|
971
|
+
&& args !== null
|
|
972
|
+
&& args.length === 2
|
|
973
|
+
&& configIndex === 0
|
|
974
|
+
&& target !== null
|
|
975
|
+
&& targetConfigValid;
|
|
976
|
+
if (!coherent) {
|
|
977
|
+
const reason = command !== 'memesh-host-claude' || args === null || configIndex !== 0 || target === null
|
|
978
|
+
? 'the command or --config declaration is malformed'
|
|
979
|
+
: 'the declared owner config target is missing, insecure, malformed, or incomplete';
|
|
980
|
+
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.');
|
|
981
|
+
}
|
|
982
|
+
return createInfo('claude-channel', 'Claude Channel registration', 'The user-scoped memesh-channel registration and owner-private config target are coherent (CONFIGURED). Development-channel admission and agent surfacing are not verified; durable MCP/inbox messaging remains a separate path.');
|
|
983
|
+
}
|
|
626
984
|
function inspectDashboardArtifact(packageRoot, existsSyncImpl) {
|
|
627
985
|
const dashboardPath = path.join(packageRoot, 'dashboard', 'dist', 'index.html');
|
|
628
986
|
if (!existsSyncImpl(dashboardPath)) {
|
|
@@ -711,7 +1069,13 @@ async function inspectUpdateStatus(packageVersion, getUpdateCheckImpl, installSu
|
|
|
711
1069
|
return createCheck('update-status', 'Update status', 'pass', `Running pre-release version (${packageVersion}), npm latest is ${update.latestVersion}`);
|
|
712
1070
|
}
|
|
713
1071
|
}
|
|
714
|
-
|
|
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'
|
|
715
1079
|
? 'Run `memesh status` while online to refresh cached update metadata.'
|
|
716
1080
|
: undefined, update.freshness === 'stale'
|
|
717
1081
|
? { code: 'update-status.stale', params: { version: packageVersion } }
|
|
@@ -742,7 +1106,7 @@ function verifySkillsManifest(packageRoot, existsSyncImpl, readFileSyncImpl, ins
|
|
|
742
1106
|
catch (err) {
|
|
743
1107
|
return createCheck('skills-manifest', 'Skills + hooks integrity', 'fail', `skills-manifest.json is unreadable (${err instanceof Error ? err.message : 'parse error'}).`, `Reinstall the package: ${reinstall} If the problem persists open an issue.`, { code: 'skills-manifest.unreadable', params: { detail: err instanceof Error ? err.message : 'parse error' } });
|
|
744
1108
|
}
|
|
745
|
-
const entries = manifest.entries
|
|
1109
|
+
const entries = Array.isArray(manifest.entries) ? manifest.entries : [];
|
|
746
1110
|
if (entries.length === 0) {
|
|
747
1111
|
return createCheck('skills-manifest', 'Skills + hooks integrity', 'fail', 'skills-manifest.json contains zero entries.', `Reinstall the package: ${reinstall}`, { code: 'skills-manifest.empty' });
|
|
748
1112
|
}
|
|
@@ -806,7 +1170,7 @@ function probeInstalledMessageCapability(packageRoot) {
|
|
|
806
1170
|
const tool = (await client.listTools()).tools.find((candidate) => candidate.name === 'message');
|
|
807
1171
|
assert.ok(tool, 'installed MCP did not advertise message');
|
|
808
1172
|
const actions = tool.inputSchema?.properties?.action?.enum;
|
|
809
|
-
assert.deepEqual(actions, ['send', 'poll', 'fetch', 'intake', 'ack', 'disposition', 'activation', 'receipts']);
|
|
1173
|
+
assert.deepEqual(actions, ['send', 'poll', 'discover', 'fetch', 'intake', 'ack', 'disposition', 'activation', 'receipts']);
|
|
810
1174
|
} finally { await client.close(); }
|
|
811
1175
|
`], {
|
|
812
1176
|
cwd: packageRoot,
|
|
@@ -825,11 +1189,11 @@ function probeInstalledMessageCapability(packageRoot) {
|
|
|
825
1189
|
}
|
|
826
1190
|
function inspectMessageCapability(packageRoot, enabled, probe) {
|
|
827
1191
|
if (!enabled) {
|
|
828
|
-
return createInfo('message-capability', 'Message adapter imports', 'Not verified (opt-in). Set MEMESH_DOCTOR_PROBE_MESSAGE_CAPABILITY=1 to start this installed MCP and verify its
|
|
1192
|
+
return createInfo('message-capability', 'Message adapter imports', 'Not verified (opt-in). Set MEMESH_DOCTOR_PROBE_MESSAGE_CAPABILITY=1 to start this installed MCP and verify its nine-action message schema plus bundled host-adapter imports. This does not check a live router socket or host registration.');
|
|
829
1193
|
}
|
|
830
1194
|
const result = probe(packageRoot);
|
|
831
1195
|
if (result.ok)
|
|
832
|
-
return createCheck('message-capability', 'Message adapter imports', 'pass', 'This installed MCP advertised the
|
|
1196
|
+
return createCheck('message-capability', 'Message adapter imports', 'pass', 'This installed MCP advertised the nine-action message schema and all bundled host adapters imported successfully. No live router socket, host registration, or host acceptance was verified.');
|
|
833
1197
|
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 } });
|
|
834
1198
|
}
|
|
835
1199
|
async function defaultMessageRouterStatusProbe() {
|
|
@@ -909,6 +1273,33 @@ async function inspectEmbeddingProbe(capabilities, probeCapabilities, embedTextI
|
|
|
909
1273
|
clearTimeout(timer);
|
|
910
1274
|
}
|
|
911
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
|
+
}
|
|
912
1303
|
async function inspectLlmProbe(capabilities, probeCapabilities, probeProviderImpl) {
|
|
913
1304
|
const llm = capabilities.llm;
|
|
914
1305
|
if (!llm) {
|
|
@@ -938,7 +1329,7 @@ function summarizeOverallStatus(checks) {
|
|
|
938
1329
|
return 'PASS';
|
|
939
1330
|
}
|
|
940
1331
|
export async function runDoctor(options) {
|
|
941
|
-
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, 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;
|
|
942
1333
|
const wasDbOpenBeforeUs = isDatabaseOpenImpl();
|
|
943
1334
|
const safeCloseDatabaseImpl = wasDbOpenBeforeUs
|
|
944
1335
|
? () => undefined
|
|
@@ -993,7 +1384,14 @@ export async function runDoctor(options) {
|
|
|
993
1384
|
? 'The vector index could not be read, so how much of your memory semantic recall can see is unknown'
|
|
994
1385
|
: `${missingVectors} memor${missingVectors === 1 ? 'y has' : 'ies have'} no search vector, `
|
|
995
1386
|
+ 'so semantic recall cannot find them (keyword search still works)';
|
|
996
|
-
|
|
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
|
+
}));
|
|
997
1395
|
}
|
|
998
1396
|
const generation = readVectorGeneration();
|
|
999
1397
|
if (generation.state !== 'none') {
|
|
@@ -1074,6 +1472,9 @@ export async function runDoctor(options) {
|
|
|
1074
1472
|
dbChecks.push(createInfo('citation_compliance', 'Memory citation rate', `${cited} of ${citationTotal} session(s) with injected memories cited at least one (${rate}%).`));
|
|
1075
1473
|
}
|
|
1076
1474
|
}
|
|
1475
|
+
const llmTelemetryHealth = inspectLlmTelemetryHealth(db);
|
|
1476
|
+
if (llmTelemetryHealth)
|
|
1477
|
+
dbChecks.push(llmTelemetryHealth);
|
|
1077
1478
|
}
|
|
1078
1479
|
catch (err) {
|
|
1079
1480
|
const message = err instanceof Error ? err.message : 'unknown database error';
|
|
@@ -1141,17 +1542,53 @@ export async function runDoctor(options) {
|
|
|
1141
1542
|
}
|
|
1142
1543
|
}
|
|
1143
1544
|
checks.push(inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl, detectCapabilitiesImpl().llm));
|
|
1144
|
-
checks.push(inspectMcpConfig(packageRoot, existsSyncImpl, readFileSyncImpl));
|
|
1545
|
+
checks.push(inspectMcpConfig(packageRoot, install, existsSyncImpl, readFileSyncImpl, envImpl));
|
|
1145
1546
|
checks.push(...inspectHooksConfig(packageRoot, platform, existsSyncImpl, readFileSyncImpl, statSyncImpl));
|
|
1146
|
-
const
|
|
1547
|
+
const pluginHost = detectPluginHost(packageRoot);
|
|
1548
|
+
let codexPluginCacheDetected = pluginHost === 'codex';
|
|
1549
|
+
let claudePluginCacheDetected = pluginHost === 'claude-code';
|
|
1550
|
+
const wiring = inspectHookWiring(existsSyncImpl, readFileSyncImpl, memeshDir(), install, installedPluginsPathImpl, pluginHost);
|
|
1551
|
+
const pluginCache = inspectPluginCacheCurrency(install, pluginHost, packageRoot, installedPluginsPathImpl, readFileSyncImpl, existsSyncImpl, marketplaceHeadShaImpl);
|
|
1552
|
+
if (pluginCache)
|
|
1553
|
+
checks.push(pluginCache);
|
|
1554
|
+
if (install === 'npm-global') {
|
|
1555
|
+
const discoveredCounts = new Map();
|
|
1556
|
+
const discoveredPluginCaches = pluginCacheDiscoveryImpl
|
|
1557
|
+
? pluginCacheDiscoveryImpl()
|
|
1558
|
+
: defaultPluginCacheDiscovery(readFileSyncImpl, existsSyncImpl);
|
|
1559
|
+
for (const discovered of discoveredPluginCaches) {
|
|
1560
|
+
if (discovered.host === 'codex')
|
|
1561
|
+
codexPluginCacheDetected = true;
|
|
1562
|
+
if (discovered.host === 'claude-code')
|
|
1563
|
+
claudePluginCacheDetected = true;
|
|
1564
|
+
let check = discovered.unverifiableReason
|
|
1565
|
+
? pluginCacheUnverifiable(discovered.host, discovered.unverifiableReason)
|
|
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
|
+
}
|
|
1570
|
+
if (check) {
|
|
1571
|
+
const hostName = discovered.host;
|
|
1572
|
+
const index = (discoveredCounts.get(discovered.host) ?? 0) + 1;
|
|
1573
|
+
discoveredCounts.set(discovered.host, index);
|
|
1574
|
+
checks.push({ ...check, id: `plugin-cache-${hostName}${index === 1 ? '' : `-${index}`}` });
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1147
1578
|
checks.push(wiring);
|
|
1579
|
+
if (claudePluginCacheDetected) {
|
|
1580
|
+
checks.push(inspectClaudeChannelRegistration(existsSyncImpl, readFileSyncImpl));
|
|
1581
|
+
}
|
|
1582
|
+
const codexSessionSetup = inspectCodexSessionSetup(codexPluginCacheDetected, existsSyncImpl);
|
|
1583
|
+
if (codexSessionSetup)
|
|
1584
|
+
checks.push(codexSessionSetup);
|
|
1148
1585
|
const captureWired = wiring.status === 'pass'
|
|
1149
1586
|
&& (wiring.params === undefined || wiring.params.captureWired === 1);
|
|
1150
1587
|
checks.push(inspectHookActivity(openDatabaseImpl, safeCloseDatabaseImpl, existsSyncImpl, statSyncImpl, captureWired));
|
|
1151
1588
|
checks.push(inspectDashboardArtifact(packageRoot, existsSyncImpl));
|
|
1152
1589
|
checks.push(inspectNodeRuntime(packageRoot, existsSyncImpl, readFileSyncImpl));
|
|
1153
1590
|
checks.push(inspectNativeBinding(packageRoot, existsSyncImpl, nativeBindingProbeImpl));
|
|
1154
|
-
checks.push(inspectShellCli(install, packageRoot, resolveShellMemeshImpl));
|
|
1591
|
+
checks.push(inspectShellCli(install, packageRoot, packageVersion, resolveShellMemeshImpl, existsSyncImpl, readFileSyncImpl));
|
|
1155
1592
|
checks.push(verifySkillsManifest(packageRoot, existsSyncImpl, readFileSyncImpl, installSupport));
|
|
1156
1593
|
checks.push(inspectMessageCapability(packageRoot, probeMessageCapability, messageCapabilityProbeImpl));
|
|
1157
1594
|
checks.push(await inspectMessageRouterStatus(probeMessageRouterStatus, messageRouterStatusProbeImpl));
|