@pcircle/memesh 4.4.0 → 4.5.1
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 +1 -1
- package/.mcp.json +1 -1
- package/README.de.md +32 -12
- package/README.es.md +13 -12
- package/README.fr.md +31 -11
- package/README.ja.md +13 -12
- package/README.ko.md +14 -13
- package/README.md +41 -16
- package/README.pt.md +30 -10
- package/README.th.md +16 -9
- package/README.vi.md +32 -12
- package/README.zh-CN.md +14 -13
- package/README.zh-TW.md +14 -13
- package/dashboard/dist/index.html +10 -10
- package/dist/cli/view-live.js +1 -1
- package/dist/cli/view.d.ts.map +1 -1
- package/dist/cli/view.js +2 -2
- package/dist/cli/view.js.map +1 -1
- package/dist/core/analytics.d.ts +7 -3
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/analytics.js +8 -8
- package/dist/core/analytics.js.map +1 -1
- package/dist/core/auto-tagger.d.ts.map +1 -1
- package/dist/core/auto-tagger.js.map +1 -1
- package/dist/core/config.d.ts +5 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +30 -14
- package/dist/core/config.js.map +1 -1
- package/dist/core/demo.d.ts +2 -2
- package/dist/core/demo.d.ts.map +1 -1
- package/dist/core/demo.js.map +1 -1
- package/dist/core/digest-validator.d.ts.map +1 -1
- package/dist/core/digest-validator.js +3 -1
- package/dist/core/digest-validator.js.map +1 -1
- package/dist/core/doctor.d.ts +3 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +255 -101
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +35 -7
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +394 -41
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts +3 -5
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +26 -95
- package/dist/core/embedder.js.map +1 -1
- package/dist/core/extractor.d.ts.map +1 -1
- package/dist/core/extractor.js +2 -1
- package/dist/core/extractor.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +2 -1
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/graph.d.ts +4 -4
- package/dist/core/graph.d.ts.map +1 -1
- package/dist/core/graph.js.map +1 -1
- package/dist/core/install-hooks.d.ts +1 -0
- package/dist/core/install-hooks.d.ts.map +1 -1
- package/dist/core/install-hooks.js +0 -0
- package/dist/core/install-hooks.js.map +1 -1
- package/dist/core/kg-backfill.d.ts +4 -4
- package/dist/core/kg-backfill.d.ts.map +1 -1
- package/dist/core/kg-backfill.js +1 -1
- package/dist/core/kg-backfill.js.map +1 -1
- package/dist/core/lesson-engine.d.ts +1 -0
- package/dist/core/lesson-engine.d.ts.map +1 -1
- package/dist/core/lesson-engine.js +1 -0
- package/dist/core/lesson-engine.js.map +1 -1
- package/dist/core/lifecycle.d.ts +4 -4
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +1 -1
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core/llm-client.d.ts.map +1 -1
- package/dist/core/llm-client.js +3 -6
- package/dist/core/llm-client.js.map +1 -1
- package/dist/core/llm-telemetry.d.ts +4 -4
- package/dist/core/llm-telemetry.d.ts.map +1 -1
- package/dist/core/llm-telemetry.js +1 -1
- package/dist/core/llm-telemetry.js.map +1 -1
- package/dist/core/llm-validator.d.ts +1 -0
- package/dist/core/llm-validator.d.ts.map +1 -1
- package/dist/core/llm-validator.js +33 -10
- 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 +4 -0
- package/dist/core/memory-tool.js.map +1 -1
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +23 -8
- package/dist/core/operations.js.map +1 -1
- package/dist/core/output-language.d.ts +6 -0
- package/dist/core/output-language.d.ts.map +1 -0
- package/dist/core/output-language.js +25 -0
- package/dist/core/output-language.js.map +1 -0
- package/dist/core/paths.d.ts +3 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +67 -1
- package/dist/core/paths.js.map +1 -1
- package/dist/core/patterns.d.ts +2 -3
- package/dist/core/patterns.d.ts.map +1 -1
- package/dist/core/patterns.js +1 -5
- package/dist/core/patterns.js.map +1 -1
- package/dist/core/project-tags.d.ts +3 -3
- package/dist/core/project-tags.d.ts.map +1 -1
- package/dist/core/project-tags.js.map +1 -1
- package/dist/core/projects.d.ts +2 -2
- package/dist/core/projects.d.ts.map +1 -1
- package/dist/core/projects.js.map +1 -1
- package/dist/core/schema-export.d.ts.map +1 -1
- package/dist/core/schema-export.js +0 -34
- package/dist/core/schema-export.js.map +1 -1
- package/dist/core/serializer.d.ts.map +1 -1
- package/dist/core/serializer.js +45 -4
- package/dist/core/serializer.js.map +1 -1
- package/dist/core/stats.d.ts +2 -2
- package/dist/core/stats.d.ts.map +1 -1
- package/dist/core/stats.js.map +1 -1
- package/dist/core/transcript-extractor.d.ts +88 -0
- package/dist/core/transcript-extractor.d.ts.map +1 -0
- package/dist/core/transcript-extractor.js +417 -0
- package/dist/core/transcript-extractor.js.map +1 -0
- package/dist/core/transcript-source.d.ts +21 -0
- package/dist/core/transcript-source.d.ts.map +1 -0
- package/dist/core/transcript-source.js +142 -0
- package/dist/core/transcript-source.js.map +1 -0
- package/dist/core/types.d.ts +17 -7
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -1
- package/dist/db.d.ts +5 -5
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +165 -17
- package/dist/db.js.map +1 -1
- package/dist/knowledge-graph.d.ts +3 -2
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +19 -10
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/mcp/server.js +2 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/skills-manifest.json +28 -33
- package/dist/storage/conflicts.d.ts +3 -3
- package/dist/storage/conflicts.d.ts.map +1 -1
- package/dist/storage/conflicts.js.map +1 -1
- package/dist/storage/fts-index.d.ts +4 -4
- package/dist/storage/fts-index.d.ts.map +1 -1
- package/dist/storage/fts-index.js.map +1 -1
- package/dist/storage/sqlite.d.ts +20 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +64 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/storage/vector-index.d.ts +3 -0
- package/dist/storage/vector-index.d.ts.map +1 -0
- package/dist/storage/vector-index.js +7 -0
- package/dist/storage/vector-index.js.map +1 -0
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +278 -91
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/retired-routes.d.ts.map +1 -1
- package/dist/transports/http/retired-routes.js +1 -0
- package/dist/transports/http/retired-routes.js.map +1 -1
- package/dist/transports/http/server.d.ts +7 -0
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +165 -79
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +4 -96
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +26 -59
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/dist/transports/schemas.d.ts +10 -30
- package/dist/transports/schemas.d.ts.map +1 -1
- package/dist/transports/schemas.js +5 -29
- package/dist/transports/schemas.js.map +1 -1
- package/hooks/hooks.json +0 -10
- package/package.json +6 -19
- package/scripts/hooks/_generated/core-paths.js +67 -1
- package/scripts/hooks/_generated/sqlite.js +71 -0
- package/scripts/hooks/_shared.js +286 -195
- package/scripts/hooks/post-commit.js +52 -10
- package/scripts/hooks/pre-compact.js +10 -7
- package/scripts/hooks/pre-edit-recall.js +4 -8
- package/scripts/hooks/session-start.js +95 -47
- package/scripts/hooks/session-summary.js +120 -52
- package/scripts/hooks/user-prompt-intent.js +3 -2
- package/skills/memesh/SKILL.md +3 -2
- package/dist/core/skill-usage-log.d.ts +0 -11
- package/dist/core/skill-usage-log.d.ts.map +0 -1
- package/dist/core/skill-usage-log.js +0 -125
- package/dist/core/skill-usage-log.js.map +0 -1
- package/dist/core/verifier.d.ts +0 -40
- package/dist/core/verifier.d.ts.map +0 -1
- package/dist/core/verifier.js +0 -206
- package/dist/core/verifier.js.map +0 -1
- package/dist/mcp/launcher.d.ts +0 -3
- package/dist/mcp/launcher.d.ts.map +0 -1
- package/dist/mcp/launcher.js +0 -37
- package/dist/mcp/launcher.js.map +0 -1
- package/scripts/hooks/pre-bash-orchestration-nudge.js +0 -155
- package/scripts/postinstall-rebuild.mjs +0 -41
- package/skills/agentic-orchestration/SKILL.md +0 -399
package/dist/core/doctor.js
CHANGED
|
@@ -3,15 +3,18 @@ import path from 'path';
|
|
|
3
3
|
import { createHash } from 'crypto';
|
|
4
4
|
import { createRequire } from 'module';
|
|
5
5
|
import { pathToFileURL } from 'url';
|
|
6
|
-
import { detectCapabilities, getConfigPath } from './config.js';
|
|
7
|
-
import { embedText
|
|
6
|
+
import { detectCapabilities, getConfigPath, isTranscriptMiningEnabled, readConfig } from './config.js';
|
|
7
|
+
import { embedText } from './embedder.js';
|
|
8
8
|
import { probeProvider } from './llm-validator.js';
|
|
9
9
|
import { openDatabase, closeDatabase, getPendingReindexInfo, isDatabaseOpen } from '../db.js';
|
|
10
10
|
import { getUpdateCheck } from './version-check.js';
|
|
11
11
|
import { getCurrentInstallChannel, getInstallChannelSupport } from './install-channel.js';
|
|
12
12
|
import { getInstallRecord } from './install-id.js';
|
|
13
|
-
import { getDbPath, memeshDir } from './paths.js';
|
|
13
|
+
import { getDbPath, memeshDir, getProjectName } from './paths.js';
|
|
14
|
+
import { lastTranscriptMineAt } from './transcript-source.js';
|
|
14
15
|
import { UNSPACED_SCRIPT_GLOB_RUN3 } from '../storage/fts-index.js';
|
|
16
|
+
import { MemeshDatabase } from '../storage/sqlite.js';
|
|
17
|
+
import { AUTO_CAPTURE_TAG } from './types.js';
|
|
15
18
|
const EMBEDDING_PROBE_TIMEOUT_MS = 15000;
|
|
16
19
|
const EXPECTED_HOOK_TYPES = ['PreToolUse', 'SessionStart', 'PostToolUse', 'Stop', 'PreCompact'];
|
|
17
20
|
const LOCALE_README_FILES = [
|
|
@@ -53,7 +56,7 @@ function inspectLocaleReadmeParity(packageRoot, existsSyncImpl, readFileSyncImpl
|
|
|
53
56
|
englishCount = countH2Headings(readFileSyncImpl(englishPath, 'utf8'));
|
|
54
57
|
}
|
|
55
58
|
catch (err) {
|
|
56
|
-
return createCheck('readme_locale_parity', 'README locale parity', 'warn', `Could not read README.md: ${err instanceof Error ? err.message : String(err)}
|
|
59
|
+
return createCheck('readme_locale_parity', 'README locale parity', 'warn', `Could not read README.md: ${err instanceof Error ? err.message : String(err)}`, undefined, { code: 'readme-parity.unreadable', params: { detail: err instanceof Error ? err.message : String(err) } });
|
|
57
60
|
}
|
|
58
61
|
const missing = [];
|
|
59
62
|
const drift = [];
|
|
@@ -86,13 +89,13 @@ function inspectLocaleReadmeParity(packageRoot, existsSyncImpl, readFileSyncImpl
|
|
|
86
89
|
.join(', ');
|
|
87
90
|
parts.push(`H2 count drift (English=${englishCount}): ${driftDetail}`);
|
|
88
91
|
}
|
|
89
|
-
return createCheck('readme_locale_parity', 'README locale parity', 'warn', parts.join('; '), `Re-sync the listed READMEs against README.md so section structure matches (±${LOCALE_H2_TOLERANCE} H2 tolerated to absorb translation collapse)
|
|
92
|
+
return createCheck('readme_locale_parity', 'README locale parity', 'warn', parts.join('; '), `Re-sync the listed READMEs against README.md so section structure matches (±${LOCALE_H2_TOLERANCE} H2 tolerated to absorb translation collapse).`, { code: 'readme-parity.drift', params: { detail: parts.join('; '), tolerance: LOCALE_H2_TOLERANCE } });
|
|
90
93
|
}
|
|
91
94
|
function resolveDatabasePath() {
|
|
92
95
|
return getDbPath();
|
|
93
96
|
}
|
|
94
|
-
function createCheck(id, label, status, summary, fix) {
|
|
95
|
-
return { id, label, status, summary, fix };
|
|
97
|
+
function createCheck(id, label, status, summary, fix, i18n) {
|
|
98
|
+
return { id, label, status, summary, fix, code: i18n?.code, params: i18n?.params };
|
|
96
99
|
}
|
|
97
100
|
function createInfo(id, label, summary, fix) {
|
|
98
101
|
return { id, label, status: 'pass', summary, fix, informational: true };
|
|
@@ -120,24 +123,32 @@ function inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl)
|
|
|
120
123
|
}
|
|
121
124
|
const parsed = parseJsonFile(configPath, readFileSyncImpl);
|
|
122
125
|
if (!parsed.ok) {
|
|
123
|
-
return createCheck('config', 'Config', 'fail', `Config file is invalid JSON at ${configPath}.`, `Fix or remove ${configPath}, then run \`memesh config list\` to confirm it loads cleanly
|
|
126
|
+
return createCheck('config', 'Config', 'fail', `Config file is invalid JSON at ${configPath}.`, `Fix or remove ${configPath}, then run \`memesh config list\` to confirm it loads cleanly.`, { code: 'config.invalid-json', params: { path: configPath } });
|
|
124
127
|
}
|
|
125
128
|
return createCheck('config', 'Config', 'pass', `Config file is readable at ${configPath}.`);
|
|
126
129
|
}
|
|
127
130
|
function inspectMcpConfig(packageRoot, existsSyncImpl, readFileSyncImpl) {
|
|
128
131
|
const mcpPath = path.join(packageRoot, '.mcp.json');
|
|
129
132
|
if (!existsSyncImpl(mcpPath)) {
|
|
130
|
-
return createCheck('mcp-config', 'MCP config', 'fail', '.mcp.json is missing.', 'Restore `.mcp.json` from the package or reinstall MeMesh.');
|
|
133
|
+
return createCheck('mcp-config', 'MCP config', 'fail', '.mcp.json is missing.', 'Restore `.mcp.json` from the package or reinstall MeMesh.', { code: 'mcp-config.missing' });
|
|
131
134
|
}
|
|
132
135
|
const parsed = parseJsonFile(mcpPath, readFileSyncImpl);
|
|
133
136
|
if (!parsed.ok) {
|
|
134
|
-
return createCheck('mcp-config', 'MCP config', 'fail', '.mcp.json is not valid JSON.', `Fix ${mcpPath} so Claude Code can read the MCP server definition
|
|
137
|
+
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 } });
|
|
135
138
|
}
|
|
136
139
|
const server = parsed.value.mcpServers?.memesh;
|
|
137
140
|
if (!server || typeof server.command !== 'string') {
|
|
138
|
-
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`.');
|
|
141
|
+
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' });
|
|
139
142
|
}
|
|
140
|
-
|
|
143
|
+
const args = Array.isArray(server.args) ? server.args : [];
|
|
144
|
+
const entry = typeof args[0] === 'string' ? args[0] : null;
|
|
145
|
+
if (entry) {
|
|
146
|
+
const resolved = path.resolve(entry.replaceAll('${CLAUDE_PLUGIN_ROOT}', packageRoot));
|
|
147
|
+
if (!existsSyncImpl(resolved)) {
|
|
148
|
+
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 } });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return createCheck('mcp-config', 'MCP config', 'pass', '.mcp.json is present, defines the memesh MCP server, and the script it starts exists.');
|
|
141
152
|
}
|
|
142
153
|
function extractHookScriptPaths(hooksConfig, packageRoot) {
|
|
143
154
|
const hooks = hooksConfig.hooks;
|
|
@@ -160,32 +171,32 @@ function inspectHooksConfig(packageRoot, platform, existsSyncImpl, readFileSyncI
|
|
|
160
171
|
const hooksPath = path.join(packageRoot, 'hooks', 'hooks.json');
|
|
161
172
|
if (!existsSyncImpl(hooksPath)) {
|
|
162
173
|
return [
|
|
163
|
-
createCheck('hooks-config', 'Hooks config', 'fail', 'hooks/hooks.json is missing.', 'Restore `hooks/hooks.json` from the package or reinstall MeMesh.'),
|
|
174
|
+
createCheck('hooks-config', 'Hooks config', 'fail', 'hooks/hooks.json is missing.', 'Restore `hooks/hooks.json` from the package or reinstall MeMesh.', { code: 'hooks-config.missing' }),
|
|
164
175
|
];
|
|
165
176
|
}
|
|
166
177
|
const parsed = parseJsonFile(hooksPath, readFileSyncImpl);
|
|
167
178
|
if (!parsed.ok) {
|
|
168
179
|
return [
|
|
169
|
-
createCheck('hooks-config', 'Hooks config', 'fail', 'hooks/hooks.json is not valid JSON.', `Fix ${hooksPath} so Claude Code can load the hook definitions
|
|
180
|
+
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 } }),
|
|
170
181
|
];
|
|
171
182
|
}
|
|
172
183
|
const hookTypes = Object.keys(parsed.value.hooks ?? {});
|
|
173
184
|
const missingTypes = EXPECTED_HOOK_TYPES.filter((type) => !hookTypes.includes(type));
|
|
174
185
|
const configCheck = missingTypes.length > 0
|
|
175
|
-
? createCheck('hooks-config', 'Hooks config', 'fail', `hooks/hooks.json is missing expected hook types: ${missingTypes.join(', ')}.`, 'Restore the shipped hook configuration or reinstall MeMesh.')
|
|
186
|
+
? 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(', ') } })
|
|
176
187
|
: createCheck('hooks-config', 'Hooks config', 'pass', `hooks/hooks.json is present with ${hookTypes.length} hook types configured.`);
|
|
177
188
|
const scriptPaths = extractHookScriptPaths(parsed.value, packageRoot);
|
|
178
189
|
if (scriptPaths.length === 0) {
|
|
179
190
|
return [
|
|
180
191
|
configCheck,
|
|
181
|
-
createCheck('hook-scripts', 'Hook scripts', 'fail', 'hooks/hooks.json parsed, but yields zero hook script commands — hooks can never fire.', 'Restore the shipped hook configuration or reinstall MeMesh.'),
|
|
192
|
+
createCheck('hook-scripts', 'Hook scripts', 'fail', 'hooks/hooks.json parsed, but yields zero hook script commands — hooks can never fire.', 'Restore the shipped hook configuration or reinstall MeMesh.', { code: 'hook-scripts.none' }),
|
|
182
193
|
];
|
|
183
194
|
}
|
|
184
195
|
const missingScripts = scriptPaths.filter((scriptPath) => !existsSyncImpl(scriptPath));
|
|
185
196
|
if (missingScripts.length > 0) {
|
|
186
197
|
return [
|
|
187
198
|
configCheck,
|
|
188
|
-
createCheck('hook-scripts', 'Hook scripts', 'fail', `Missing hook scripts: ${missingScripts.map((entry) => path.relative(packageRoot, entry)).join(', ')}.`, 'Restore the missing files from the package or reinstall MeMesh.'),
|
|
199
|
+
createCheck('hook-scripts', 'Hook scripts', 'fail', `Missing hook scripts: ${missingScripts.map((entry) => path.relative(packageRoot, entry)).join(', ')}.`, 'Restore the missing files from the package or reinstall MeMesh.', { code: 'hook-scripts.missing', params: { files: missingScripts.map((entry) => path.relative(packageRoot, entry)).join(', ') } }),
|
|
189
200
|
];
|
|
190
201
|
}
|
|
191
202
|
if (platform !== 'win32') {
|
|
@@ -196,7 +207,7 @@ function inspectHooksConfig(packageRoot, platform, existsSyncImpl, readFileSyncI
|
|
|
196
207
|
if (nonExecutable.length > 0) {
|
|
197
208
|
return [
|
|
198
209
|
configCheck,
|
|
199
|
-
createCheck('hook-scripts', 'Hook scripts', 'fail', `Hook scripts are not executable: ${nonExecutable.map((entry) => path.relative(packageRoot, entry)).join(', ')}.`, 'Run `npm run build` from the repo checkout or `chmod +x scripts/hooks/*.js` for a local repair.'),
|
|
210
|
+
createCheck('hook-scripts', 'Hook scripts', 'fail', `Hook scripts are not executable: ${nonExecutable.map((entry) => path.relative(packageRoot, entry)).join(', ')}.`, 'Run `npm run build` from the repo checkout or `chmod +x scripts/hooks/*.js` for a local repair.', { code: 'hook-scripts.not-executable', params: { files: nonExecutable.map((entry) => path.relative(packageRoot, entry)).join(', ') } }),
|
|
200
211
|
];
|
|
201
212
|
}
|
|
202
213
|
}
|
|
@@ -205,84 +216,170 @@ function inspectHooksConfig(packageRoot, platform, existsSyncImpl, readFileSyncI
|
|
|
205
216
|
createCheck('hook-scripts', 'Hook scripts', 'pass', `All ${scriptPaths.length} hook scripts are present${platform === 'win32' ? '' : ' and executable'}.`),
|
|
206
217
|
];
|
|
207
218
|
}
|
|
208
|
-
function inspectHookWiring(existsSyncImpl, readFileSyncImpl, memeshDir,
|
|
219
|
+
function inspectHookWiring(existsSyncImpl, readFileSyncImpl, memeshDir, installChannel) {
|
|
209
220
|
const markerPath = path.join(memeshDir, 'install-hooks.json');
|
|
210
221
|
if (!existsSyncImpl(markerPath)) {
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
if (existsSyncImpl(pluginManifest)) {
|
|
214
|
-
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'pass', 'Wired via Claude Code plugin runtime (.claude-plugin/plugin.json present). The install-hooks marker is not used on this install path.');
|
|
215
|
-
}
|
|
222
|
+
if (installChannel === 'plugin-marketplace') {
|
|
223
|
+
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'pass', 'Wired via the Claude Code plugin runtime (this is a plugin-marketplace install). The install-hooks marker is not used on this install path.');
|
|
216
224
|
}
|
|
217
|
-
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'warn', '
|
|
225
|
+
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'warn', 'memesh is not connected to Claude Code yet, so nothing gets remembered automatically from your sessions.', 'Run `memesh install-hooks` once to connect it, then `memesh doctor` to confirm.', { code: 'hook-wiring.no-marker' });
|
|
218
226
|
}
|
|
219
227
|
const parsed = parseJsonFile(markerPath, readFileSyncImpl);
|
|
220
228
|
if (!parsed.ok) {
|
|
221
|
-
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'warn', `install-hooks marker at ${markerPath} is unreadable.`, 'Re-run `memesh install-hooks` to refresh the marker.');
|
|
229
|
+
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'warn', `install-hooks marker at ${markerPath} is unreadable.`, 'Re-run `memesh install-hooks` to refresh the marker.', { code: 'hook-wiring.marker-unreadable', params: { path: markerPath } });
|
|
222
230
|
}
|
|
223
231
|
const marker = parsed.value;
|
|
224
232
|
if (typeof marker.settings_path !== 'string') {
|
|
225
|
-
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'warn', 'install-hooks marker is malformed (missing settings_path).', 'Re-run `memesh install-hooks`.');
|
|
233
|
+
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'warn', 'install-hooks marker is malformed (missing settings_path).', 'Re-run `memesh install-hooks`.', { code: 'hook-wiring.marker-malformed' });
|
|
226
234
|
}
|
|
227
235
|
if (!existsSyncImpl(marker.settings_path)) {
|
|
228
|
-
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'fail', `Marker recorded settings at ${marker.settings_path} but the file no longer exists. Hooks are not wired.`, 'Re-run `memesh install-hooks`.');
|
|
236
|
+
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'fail', `Marker recorded settings at ${marker.settings_path} but the file no longer exists. Hooks are not wired.`, 'Re-run `memesh install-hooks`.', { code: 'hook-wiring.settings-missing', params: { path: String(marker.settings_path) } });
|
|
229
237
|
}
|
|
230
238
|
const settingsParsed = parseJsonFile(marker.settings_path, readFileSyncImpl);
|
|
231
239
|
if (!settingsParsed.ok) {
|
|
232
|
-
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'fail', `${marker.settings_path} is no longer valid JSON.`, 'Restore from your ~/.claude backups or re-create with `memesh install-hooks`.');
|
|
240
|
+
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'fail', `${marker.settings_path} is no longer valid JSON.`, 'Restore from your ~/.claude backups or re-create with `memesh install-hooks`.', { code: 'hook-wiring.settings-invalid', params: { path: String(marker.settings_path) } });
|
|
233
241
|
}
|
|
242
|
+
const CAPTURE_EVENTS = new Set(['Stop', 'PostToolUse', 'PreCompact']);
|
|
234
243
|
const hooks = settingsParsed.value.hooks;
|
|
235
244
|
let hasMemeshHook = false;
|
|
245
|
+
let hasCaptureHook = false;
|
|
246
|
+
let missingScript = null;
|
|
236
247
|
if (hooks && typeof hooks === 'object') {
|
|
237
|
-
for (const entries of Object.
|
|
248
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
238
249
|
if (!Array.isArray(entries))
|
|
239
250
|
continue;
|
|
240
251
|
for (const entry of entries) {
|
|
241
252
|
const cmds = entry.hooks;
|
|
242
253
|
if (!Array.isArray(cmds))
|
|
243
254
|
continue;
|
|
244
|
-
|
|
255
|
+
for (const c of cmds) {
|
|
256
|
+
const cmd = c;
|
|
257
|
+
if (cmd._memesh !== true)
|
|
258
|
+
continue;
|
|
245
259
|
hasMemeshHook = true;
|
|
246
|
-
|
|
260
|
+
if (CAPTURE_EVENTS.has(event))
|
|
261
|
+
hasCaptureHook = true;
|
|
262
|
+
if (missingScript === null && typeof cmd.command === 'string'
|
|
263
|
+
&& path.isAbsolute(cmd.command) && !existsSyncImpl(cmd.command)) {
|
|
264
|
+
missingScript = cmd.command;
|
|
265
|
+
}
|
|
247
266
|
}
|
|
248
267
|
}
|
|
249
|
-
if (hasMemeshHook)
|
|
250
|
-
break;
|
|
251
268
|
}
|
|
252
269
|
}
|
|
270
|
+
if (missingScript !== null) {
|
|
271
|
+
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'fail', `A wired memesh hook points at ${missingScript}, which no longer exists — the agent invokes a missing file on every matching event. This is usually residue from an upgrade that retired the hook.`, 'Run `memesh install-hooks` to re-wire (it now removes retired entries), then restart your agent.', { code: 'hook-wiring.script-missing', params: { path: missingScript } });
|
|
272
|
+
}
|
|
253
273
|
if (!hasMemeshHook) {
|
|
254
|
-
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'fail', `Marker recorded a memesh install at ${marker.settings_path}, but no _memesh:true hook entries are present anymore. Settings drifted (manual edit?) or memesh was uninstalled out-of-band.`, 'Re-run `memesh install-hooks` to re-wire.');
|
|
274
|
+
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'fail', `Marker recorded a memesh install at ${marker.settings_path}, but no _memesh:true hook entries are present anymore. Settings drifted (manual edit?) or memesh was uninstalled out-of-band.`, 'Re-run `memesh install-hooks` to re-wire.', { code: 'hook-wiring.entries-removed', params: { path: String(marker.settings_path) } });
|
|
255
275
|
}
|
|
256
276
|
if (typeof marker.plugin_root === 'string' && !existsSyncImpl(marker.plugin_root)) {
|
|
257
|
-
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'fail', `Hook commands point at ${marker.plugin_root}, which no longer exists (likely after an npm-global path change).`, 'Re-run `memesh install-hooks` to refresh paths.');
|
|
277
|
+
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'fail', `Hook commands point at ${marker.plugin_root}, which no longer exists (likely after an npm-global path change).`, 'Re-run `memesh install-hooks` to refresh paths.', { code: 'hook-wiring.root-moved', params: { path: String(marker.plugin_root) } });
|
|
258
278
|
}
|
|
259
|
-
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'pass', `Wired in ${marker.settings_path} (scope: ${marker.scope ?? 'user'}, version: ${marker.version ?? 'unknown'})
|
|
279
|
+
return createCheck('hook-wiring', 'Hooks wired into Claude Code', 'pass', `Wired in ${marker.settings_path} (scope: ${marker.scope ?? 'user'}, version: ${marker.version ?? 'unknown'}).`, undefined, { params: { captureWired: hasCaptureHook ? 1 : 0 } });
|
|
260
280
|
}
|
|
261
|
-
function inspectHookActivity(openDatabaseImpl, closeDatabaseImpl, existsSyncImpl = fs.existsSync, statSyncImpl = fs.statSync) {
|
|
281
|
+
function inspectHookActivity(openDatabaseImpl, closeDatabaseImpl, existsSyncImpl = fs.existsSync, statSyncImpl = fs.statSync, wiringPresent = true) {
|
|
282
|
+
const TITLE = 'Hook activity';
|
|
283
|
+
const captureOff = autoCaptureOffSource();
|
|
284
|
+
if (captureOff === 'config') {
|
|
285
|
+
return createCheck('hook-activity', TITLE, 'pass', 'Automatic capture is turned off (config autoCapture: false) — deliberately, so hook silence is expected. Re-enable it to resume capturing sessions.', undefined, { code: 'hook-activity.disabled' });
|
|
286
|
+
}
|
|
287
|
+
if (captureOff === 'env') {
|
|
288
|
+
return createCheck('hook-activity', TITLE, 'pass', 'Automatic capture is turned off by MEMESH_AUTO_CAPTURE=false in this shell\'s environment. Doctor can only see its own environment — if your agent runs without this variable, capture there is unaffected.', undefined, { code: 'hook-activity.disabled-env' });
|
|
289
|
+
}
|
|
262
290
|
let db = null;
|
|
263
291
|
try {
|
|
264
292
|
db = openDatabaseImpl();
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
293
|
+
const hookRunsTablePresent = !!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'hook_runs'").get();
|
|
294
|
+
const rows = hookRunsTablePresent
|
|
295
|
+
? db.prepare(`SELECT hook, last_run_at FROM hook_runs`).all()
|
|
296
|
+
: [];
|
|
297
|
+
const captured = db.prepare(`SELECT COUNT(DISTINCT e.id) as c FROM entities e
|
|
298
|
+
JOIN tags t ON t.entity_id = e.id
|
|
299
|
+
WHERE t.tag = ?
|
|
300
|
+
AND e.created_at > datetime('now', '-24 hours')`).get(AUTO_CAPTURE_TAG)?.c ?? 0;
|
|
301
|
+
const since = db.prepare("SELECT value FROM memesh_metadata WHERE key = 'hook_runs_since'").get()?.value;
|
|
302
|
+
const measuringHours = since !== undefined ? hoursSince(since) : null;
|
|
303
|
+
const SKEW_TOLERANCE_H = 5 / 60;
|
|
304
|
+
const KNOWN_HOOKS = new Set(['session-summary', 'post-commit', 'pre-compact']);
|
|
305
|
+
const ages = new Map();
|
|
306
|
+
for (const r of rows) {
|
|
307
|
+
if (!KNOWN_HOOKS.has(r.hook))
|
|
308
|
+
continue;
|
|
309
|
+
const age = hoursSince(r.last_run_at);
|
|
310
|
+
ages.set(r.hook, age !== null && age >= -SKEW_TOLERANCE_H ? Math.max(age, 0) : null);
|
|
311
|
+
}
|
|
312
|
+
if (ages.size > 0) {
|
|
313
|
+
const capturedTail = captured > 0
|
|
314
|
+
? `${captured} memor${captured === 1 ? 'y' : 'ies'} captured in the last 24h.`
|
|
315
|
+
: 'Nothing was worth saving in that time, which is normal — the loop still ran.';
|
|
316
|
+
const staleUnknown = (hook) => createCheck('hook-activity', TITLE, 'fail', `The ${hook} hook's last-run timestamp cannot be read (corrupt, or stamped by a machine with a wrong clock). ` +
|
|
317
|
+
'Capture health is unknown, which is not the same as healthy.', 'End one work session, then re-run `memesh doctor` — a fresh run overwrites the bad timestamp.', { code: 'hook-activity.stale-unknown', params: { hook } });
|
|
318
|
+
const stale = (hook, age, status) => createCheck('hook-activity', TITLE, status, `The ${hook} hook last ran ${formatHoursAgo(Math.ceil(age))}. ` +
|
|
319
|
+
'If you have worked since then, capture has stopped and nothing from those sessions was saved.', 'Restart your agent, then end one session and re-run `memesh doctor`. If it does not recover, run `memesh install-hooks`.', { code: 'hook-activity.stale', params: { hook, hours: Math.ceil(age) } });
|
|
320
|
+
const ssAge = ages.has('session-summary') ? ages.get('session-summary') : undefined;
|
|
321
|
+
if (ssAge === null)
|
|
322
|
+
return staleUnknown('session-summary');
|
|
323
|
+
if (ssAge !== undefined) {
|
|
324
|
+
if (ssAge <= 24) {
|
|
325
|
+
return createCheck('hook-activity', TITLE, 'pass', `The session-summary hook last ran ${formatHoursAgo(ssAge)} — auto-capture is alive. ${capturedTail}`);
|
|
326
|
+
}
|
|
327
|
+
if (ssAge <= 72)
|
|
328
|
+
return stale('session-summary', ssAge, 'warn');
|
|
329
|
+
const ccWrites = db.prepare(`SELECT COUNT(*) as c FROM entities
|
|
330
|
+
WHERE created_at > datetime('now', '-72 hours')
|
|
331
|
+
AND json_valid(metadata)
|
|
332
|
+
AND json_extract(metadata, '$.provenance.source_host') = 'claude-code'`).get()?.c ?? 0;
|
|
333
|
+
if (ccWrites > 0)
|
|
334
|
+
return stale('session-summary', ssAge, 'fail');
|
|
335
|
+
return createCheck('hook-activity', TITLE, 'warn', `The session-summary hook last ran ${formatHoursAgo(Math.ceil(ssAge))}, and no Claude Code writes landed in the last 3 days either. If this machine has moved to another agent (Codex / Gemini), this is expected; if you still use Claude Code here, capture has stopped.`, 'If you still use Claude Code on this machine: restart it, end one session, and re-run `memesh doctor`. If it does not recover, run `memesh install-hooks`.', { code: 'hook-activity.stale-unconfirmed', params: { hook: 'session-summary', hours: Math.ceil(ssAge) } });
|
|
336
|
+
}
|
|
337
|
+
const known = [...ages.entries()].filter((e) => e[1] !== null);
|
|
338
|
+
if (known.length === 0) {
|
|
339
|
+
const [hook] = [...ages.keys()].sort();
|
|
340
|
+
return staleUnknown(hook);
|
|
341
|
+
}
|
|
342
|
+
const [freshestHook, freshestAge] = known.sort((a, b) => a[1] - b[1])[0];
|
|
343
|
+
if (freshestAge <= 24) {
|
|
344
|
+
if (measuringHours !== null && measuringHours > 72) {
|
|
345
|
+
return createCheck('hook-activity', TITLE, 'warn', `The ${freshestHook} hook is stamping (last ran ${formatHoursAgo(freshestAge)}), but the session-summary hook has never run once in the ${Math.round(measuringHours)} hours since tracking began — session capture may be broken while commit capture hides it.`, 'End one work session and re-run `memesh doctor`. If session-summary still has not run, run `memesh install-hooks` and restart your agent.', { code: 'hook-activity.stop-silent', params: { hook: freshestHook, hours: Math.round(measuringHours) } });
|
|
277
346
|
}
|
|
278
|
-
|
|
347
|
+
return createCheck('hook-activity', TITLE, 'pass', `The ${freshestHook} hook last ran ${formatHoursAgo(freshestAge)} — auto-capture is alive. ` +
|
|
348
|
+
`(session-summary has not stamped yet — expected until a session with real work ends.) ${capturedTail}`);
|
|
279
349
|
}
|
|
280
|
-
return
|
|
350
|
+
return stale(freshestHook, freshestAge, 'warn');
|
|
351
|
+
}
|
|
352
|
+
if (since !== undefined && (measuringHours === null || measuringHours < 0)) {
|
|
353
|
+
return createCheck('hook-activity', TITLE, 'pass', 'The hook-run tracking marker is unreadable — it will be re-stamped automatically the next time the database is opened for writing (any session, commit, or memesh command does it). Tracking restarts then.');
|
|
281
354
|
}
|
|
282
|
-
|
|
355
|
+
if (measuringHours === null || measuringHours < 24) {
|
|
356
|
+
return createCheck('hook-activity', TITLE, 'pass', 'Hook-run tracking has only just started on this database — the first work session will fill it in.');
|
|
357
|
+
}
|
|
358
|
+
const markerPath = path.join(memeshDir(), 'install-hooks.json');
|
|
359
|
+
if (existsSyncImpl(markerPath)) {
|
|
360
|
+
try {
|
|
361
|
+
if (Date.now() - statSyncImpl(markerPath).mtimeMs < 24 * 60 * 60 * 1000) {
|
|
362
|
+
return createCheck('hook-activity', TITLE, 'pass', 'Hooks were wired in the last day and no session has ended yet — normal for a fresh install.');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch { }
|
|
366
|
+
}
|
|
367
|
+
const legacyCaptured = db.prepare(`SELECT COUNT(DISTINCT e.id) as c FROM entities e
|
|
368
|
+
JOIN tags t ON t.entity_id = e.id
|
|
369
|
+
WHERE t.tag = ?
|
|
370
|
+
AND e.created_at > ?`).get(AUTO_CAPTURE_TAG, since)?.c ?? 0;
|
|
371
|
+
if (legacyCaptured > 0) {
|
|
372
|
+
return createCheck('hook-activity', TITLE, 'warn', `No hook has stamped the heartbeat, but ${legacyCaptured} auto-capture memor${legacyCaptured === 1 ? 'y' : 'ies'} landed since tracking began — hooks from a version before heartbeat tracking are probably still running.`, 'Update the memesh hooks to the current version (plugin installs: `/plugin update memesh`; npm installs: `memesh install-hooks`), then restart your agent.', { code: 'hook-activity.never-ran-legacy', params: { captured: legacyCaptured } });
|
|
373
|
+
}
|
|
374
|
+
if (!wiringPresent) {
|
|
375
|
+
return createCheck('hook-activity', TITLE, 'warn', 'No capture hook has ever run — and no capture hook (Stop / PostToolUse / PreCompact) is confirmed wired on this machine, so there is nothing to run.', 'If you want automatic capture, run `memesh install-hooks`. If this install is MCP-only (Codex / Gemini), this is expected and safe to ignore.', { code: 'hook-activity.not-wired' });
|
|
376
|
+
}
|
|
377
|
+
return createCheck('hook-activity', TITLE, 'fail', `No capture hook has run since tracking began ${formatHoursAgo(measuringHours)}. ` +
|
|
378
|
+
'Hook wiring is in place, so they should be executing and are not — nothing is being remembered.', 'Run `memesh doctor` after ending one work session. If this still says no hook has run, run `memesh install-hooks` and restart your agent.', { code: 'hook-activity.never-ran', params: { hours: Math.round(measuringHours) } });
|
|
283
379
|
}
|
|
284
380
|
catch (err) {
|
|
285
|
-
|
|
381
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
382
|
+
return createCheck('hook-activity', TITLE, 'fail', `Could not read hook activity from the database: ${detail}. Capture health is unknown, which is not the same as healthy.`, 'Run `memesh doctor --verbose` for the full error, and check that ~/.memesh is readable and not out of disk.', { code: 'hook-activity.query-failed', params: { detail } });
|
|
286
383
|
}
|
|
287
384
|
finally {
|
|
288
385
|
try {
|
|
@@ -292,6 +389,44 @@ function inspectHookActivity(openDatabaseImpl, closeDatabaseImpl, existsSyncImpl
|
|
|
292
389
|
catch { }
|
|
293
390
|
}
|
|
294
391
|
}
|
|
392
|
+
function autoCaptureOffSource() {
|
|
393
|
+
const envVal = process.env.MEMESH_AUTO_CAPTURE;
|
|
394
|
+
if (envVal === 'false')
|
|
395
|
+
return 'env';
|
|
396
|
+
if (envVal === 'true')
|
|
397
|
+
return null;
|
|
398
|
+
try {
|
|
399
|
+
return readConfig().autoCapture === false ? 'config' : null;
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
export function hoursSince(sqliteTimestamp) {
|
|
406
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})$/.exec(sqliteTimestamp ?? '');
|
|
407
|
+
if (!m)
|
|
408
|
+
return null;
|
|
409
|
+
const then = Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]);
|
|
410
|
+
if (!Number.isFinite(then))
|
|
411
|
+
return null;
|
|
412
|
+
const d = new Date(then);
|
|
413
|
+
if (d.getUTCFullYear() !== +m[1] || d.getUTCMonth() !== +m[2] - 1 || d.getUTCDate() !== +m[3]
|
|
414
|
+
|| d.getUTCHours() !== +m[4] || d.getUTCMinutes() !== +m[5] || d.getUTCSeconds() !== +m[6])
|
|
415
|
+
return null;
|
|
416
|
+
return (Date.now() - then) / (60 * 60 * 1000);
|
|
417
|
+
}
|
|
418
|
+
function formatHoursAgo(hours) {
|
|
419
|
+
if (hours === null || !Number.isFinite(hours) || hours < 0)
|
|
420
|
+
return 'at an unknown time';
|
|
421
|
+
if (hours < 1)
|
|
422
|
+
return 'less than an hour ago';
|
|
423
|
+
if (hours < 48) {
|
|
424
|
+
const h = Math.round(hours);
|
|
425
|
+
return `${h} hour${h === 1 ? '' : 's'} ago`;
|
|
426
|
+
}
|
|
427
|
+
const d = Math.round(hours / 24);
|
|
428
|
+
return `${d} day${d === 1 ? '' : 's'} ago`;
|
|
429
|
+
}
|
|
295
430
|
function defaultResolveShellMemesh() {
|
|
296
431
|
try {
|
|
297
432
|
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
@@ -305,12 +440,23 @@ function defaultResolveShellMemesh() {
|
|
|
305
440
|
return null;
|
|
306
441
|
}
|
|
307
442
|
}
|
|
443
|
+
const RUNTIME_TOO_OLD = 'memesh:node-sqlite-too-old';
|
|
308
444
|
function defaultNativeBindingProbe(packageRoot) {
|
|
309
445
|
try {
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
446
|
+
const probe = new MemeshDatabase(':memory:', { allowExtension: true });
|
|
447
|
+
try {
|
|
448
|
+
if (typeof probe.enableLoadExtension !== 'function') {
|
|
449
|
+
return { ok: false, message: `${RUNTIME_TOO_OLD}: node:sqlite in ${process.version} has no enableLoadExtension` };
|
|
450
|
+
}
|
|
451
|
+
probe.enableLoadExtension(true);
|
|
452
|
+
const localRequire = createRequire(pathToFileURL(path.join(packageRoot, 'package.json')).href);
|
|
453
|
+
const sqliteVec = localRequire('sqlite-vec');
|
|
454
|
+
sqliteVec.load(probe);
|
|
455
|
+
probe.prepare('SELECT vec_version()').get();
|
|
456
|
+
}
|
|
457
|
+
finally {
|
|
458
|
+
probe.close();
|
|
459
|
+
}
|
|
314
460
|
return { ok: true };
|
|
315
461
|
}
|
|
316
462
|
catch (err) {
|
|
@@ -356,7 +502,7 @@ export function inspectNodeRuntime(packageRoot, existsSyncImpl, readFileSyncImpl
|
|
|
356
502
|
}
|
|
357
503
|
if (!ok) {
|
|
358
504
|
return createCheck('node-runtime', 'Node runtime', 'fail', `${facts} This package requires Node ${declared}, so this runtime is BELOW the ` +
|
|
359
|
-
`supported floor. Native modules and hooks may fail in ways that look unrelated.`, `Upgrade Node to ${declared.replace(/^>=\s*/, '')} or newer, then run \`memesh doctor\` again
|
|
505
|
+
`supported floor. Native modules and hooks may fail in ways that look unrelated.`, `Upgrade Node to ${declared.replace(/^>=\s*/, '')} or newer, then run \`memesh doctor\` again.`, { code: 'node-runtime.too-old', params: { detail: facts, required: declared.replace(/^>=\s*/, '') } });
|
|
360
506
|
}
|
|
361
507
|
return createCheck('node-runtime', 'Node runtime', 'pass', `${facts} Meets the required range ${declared}.`);
|
|
362
508
|
}
|
|
@@ -372,20 +518,16 @@ export function hasBuiltInSqlite() {
|
|
|
372
518
|
function inspectNativeBinding(packageRoot, _existsSyncImpl, probeImpl = defaultNativeBindingProbe) {
|
|
373
519
|
const result = probeImpl(packageRoot);
|
|
374
520
|
if (result.ok) {
|
|
375
|
-
return createCheck('native-binding', '
|
|
521
|
+
return createCheck('native-binding', 'SQLite and vector search', 'pass', 'node:sqlite opened a database and sqlite-vec loaded (probe succeeded).');
|
|
522
|
+
}
|
|
523
|
+
if (result.message.startsWith(RUNTIME_TOO_OLD)) {
|
|
524
|
+
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 } });
|
|
376
525
|
}
|
|
377
|
-
const isMissingBinding = /bindings file|locate the bindings/i.test(result.message);
|
|
378
526
|
const isMissingPackage = /MODULE_NOT_FOUND|Cannot find module/i.test(result.message);
|
|
379
527
|
if (isMissingPackage) {
|
|
380
|
-
return createCheck('native-binding', '
|
|
381
|
-
+ 'parent node_modules). Memesh hooks and database operations will not work.', `Run: npm install (in the directory that depends on @pcircle/memesh)`);
|
|
382
|
-
}
|
|
383
|
-
if (isMissingBinding) {
|
|
384
|
-
return createCheck('native-binding', 'Native SQLite binding', 'fail', 'better-sqlite3 is installed but the native binding (.node file) is missing. '
|
|
385
|
-
+ 'Hooks will silently skip-and-exit, and auto-capture will NOT write any entities. '
|
|
386
|
-
+ 'This is the plugin-marketplace silent-dropout class of bug.', `Run: cd "${packageRoot}" && npm rebuild better-sqlite3 (or "npm install --omit=dev" for a clean reinstall)`);
|
|
528
|
+
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' });
|
|
387
529
|
}
|
|
388
|
-
return createCheck('native-binding', '
|
|
530
|
+
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 } });
|
|
389
531
|
}
|
|
390
532
|
function inspectShellCli(installChannel, packageRoot, resolveShellMemeshImpl) {
|
|
391
533
|
const shellPath = resolveShellMemeshImpl();
|
|
@@ -401,7 +543,7 @@ function inspectShellCli(installChannel, packageRoot, resolveShellMemeshImpl) {
|
|
|
401
543
|
}
|
|
402
544
|
if (installChannel === 'plugin-marketplace') {
|
|
403
545
|
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`. '
|
|
404
|
-
+ 'Claude Code 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`.');
|
|
546
|
+
+ 'Claude Code 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' });
|
|
405
547
|
}
|
|
406
548
|
return createCheck('shell-cli', 'Shell CLI on PATH', 'pass', shellPath
|
|
407
549
|
? `\`memesh\` resolves to ${shellPath}.`
|
|
@@ -410,14 +552,14 @@ function inspectShellCli(installChannel, packageRoot, resolveShellMemeshImpl) {
|
|
|
410
552
|
function inspectDashboardArtifact(packageRoot, existsSyncImpl) {
|
|
411
553
|
const dashboardPath = path.join(packageRoot, 'dashboard', 'dist', 'index.html');
|
|
412
554
|
if (!existsSyncImpl(dashboardPath)) {
|
|
413
|
-
return createCheck('dashboard', 'Dashboard artifact', 'fail', 'dashboard/dist/index.html is missing.', 'Build the dashboard with `cd dashboard && npm install && npm run build`, then run `npm run build` at the repo root if needed.');
|
|
555
|
+
return createCheck('dashboard', 'Dashboard artifact', 'fail', 'dashboard/dist/index.html is missing.', 'Build the dashboard with `cd dashboard && npm install && npm run build`, then run `npm run build` at the repo root if needed.', { code: 'dashboard.missing' });
|
|
414
556
|
}
|
|
415
557
|
return createCheck('dashboard', 'Dashboard artifact', 'pass', 'dashboard/dist/index.html is present.');
|
|
416
558
|
}
|
|
417
559
|
async function inspectUpdateStatus(packageVersion, getUpdateCheckImpl, installSupport) {
|
|
418
560
|
const update = await getUpdateCheckImpl(packageVersion, { preferFresh: false });
|
|
419
561
|
if (!update) {
|
|
420
|
-
return createCheck('update-status', 'Update status', 'warn', '
|
|
562
|
+
return createCheck('update-status', 'Update status', 'warn', 'memesh has not been able to check for newer versions yet, so it cannot tell you whether an update exists.', 'Run `memesh status` once while connected to the internet — that stores the answer and this notice goes away.', { code: 'update-status.no-cache' });
|
|
421
563
|
}
|
|
422
564
|
if (update.currentVersionDeprecated && update.deprecationMessage) {
|
|
423
565
|
const target = update.latestVersion && update.latestVersion !== packageVersion
|
|
@@ -439,10 +581,10 @@ async function inspectUpdateStatus(packageVersion, getUpdateCheckImpl, installSu
|
|
|
439
581
|
else {
|
|
440
582
|
fix = `Upgrade via your install method (see \`memesh status\`).`;
|
|
441
583
|
}
|
|
442
|
-
return createCheck('update-status', 'Update status', 'fail', `Installed version ${packageVersion} is DEPRECATED by maintainers: ${update.deprecationMessage}`, fix);
|
|
584
|
+
return createCheck('update-status', 'Update status', 'fail', `Installed version ${packageVersion} is DEPRECATED by maintainers: ${update.deprecationMessage}`, fix, { code: 'update-status.deprecated', params: { version: packageVersion, detail: update.deprecationMessage ?? '' } });
|
|
443
585
|
}
|
|
444
586
|
if (update.freshness === 'unavailable') {
|
|
445
|
-
return createCheck('update-status', 'Update status', 'warn', '
|
|
587
|
+
return createCheck('update-status', 'Update status', 'warn', 'memesh has not been able to check for newer versions yet, so it cannot tell you whether an update exists.', 'Run `memesh status` once while connected to the internet — that stores the answer and this notice goes away.', { code: 'update-status.no-cache' });
|
|
446
588
|
}
|
|
447
589
|
if (update.checkSucceeded && update.lastError) {
|
|
448
590
|
const hasUpdate = Boolean(update.updateAvailable && update.latestVersion);
|
|
@@ -465,11 +607,14 @@ async function inspectUpdateStatus(packageVersion, getUpdateCheckImpl, installSu
|
|
|
465
607
|
upgradeHint = '';
|
|
466
608
|
}
|
|
467
609
|
const fix = `Run \`memesh status\` while online to retry the deprecation lookup${upgradeHint}.`;
|
|
468
|
-
return createCheck('update-status', 'Update status', 'warn', summary, fix
|
|
610
|
+
return createCheck('update-status', 'Update status', 'warn', summary, fix, {
|
|
611
|
+
code: 'update-status.deprecation-unknown',
|
|
612
|
+
params: { version: packageVersion, detail: update.lastError ?? '' },
|
|
613
|
+
});
|
|
469
614
|
}
|
|
470
615
|
if (update.updateAvailable && update.latestVersion) {
|
|
471
616
|
if (packageVersion < update.latestVersion) {
|
|
472
|
-
return createCheck('update-status', 'Update status', 'warn', `Update available: ${update.latestVersion} (current: ${packageVersion})`, `Run 'memesh update' to upgrade
|
|
617
|
+
return createCheck('update-status', 'Update status', 'warn', `Update available: ${update.latestVersion} (current: ${packageVersion})`, `Run 'memesh update' to upgrade`, { code: 'update-status.update-available', params: { latest: update.latestVersion, current: packageVersion } });
|
|
473
618
|
}
|
|
474
619
|
else {
|
|
475
620
|
return createCheck('update-status', 'Update status', 'pass', `Running pre-release version (${packageVersion}), npm latest is ${update.latestVersion}`);
|
|
@@ -477,35 +622,37 @@ async function inspectUpdateStatus(packageVersion, getUpdateCheckImpl, installSu
|
|
|
477
622
|
}
|
|
478
623
|
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'
|
|
479
624
|
? 'Run `memesh status` while online to refresh cached update metadata.'
|
|
625
|
+
: undefined, update.freshness === 'stale'
|
|
626
|
+
? { code: 'update-status.stale', params: { version: packageVersion } }
|
|
480
627
|
: undefined);
|
|
481
628
|
}
|
|
482
629
|
async function inspectHttpProbe(httpBaseUrl, fetchImpl) {
|
|
483
630
|
try {
|
|
484
631
|
const response = await fetchImpl(`${httpBaseUrl.replace(/\/$/, '')}/v1/health`);
|
|
485
632
|
if (!response.ok) {
|
|
486
|
-
return createCheck('http-probe', 'HTTP probe', 'warn', `HTTP server responded with ${response.status} at ${httpBaseUrl}.`, 'Run `memesh serve` and check the logs, then retry `memesh doctor --probe-http`.');
|
|
633
|
+
return createCheck('http-probe', 'HTTP probe', 'warn', `HTTP server responded with ${response.status} at ${httpBaseUrl}.`, 'Run `memesh serve` and check the logs, then retry `memesh doctor --probe-http`.', { code: 'http-probe.bad-status', params: { status: response.status, url: httpBaseUrl } });
|
|
487
634
|
}
|
|
488
635
|
return createCheck('http-probe', 'HTTP probe', 'pass', `HTTP server is reachable at ${httpBaseUrl}.`);
|
|
489
636
|
}
|
|
490
637
|
catch {
|
|
491
|
-
return createCheck('http-probe', 'HTTP probe', 'warn', `No running HTTP server detected at ${httpBaseUrl}.`, 'Start the local server with `memesh serve` if you want dashboard and HTTP API verification.');
|
|
638
|
+
return createCheck('http-probe', 'HTTP probe', 'warn', `No running HTTP server detected at ${httpBaseUrl}.`, 'Start the local server with `memesh serve` if you want dashboard and HTTP API verification.', { code: 'http-probe.no-server', params: { url: httpBaseUrl } });
|
|
492
639
|
}
|
|
493
640
|
}
|
|
494
641
|
function verifySkillsManifest(packageRoot, existsSyncImpl, readFileSyncImpl) {
|
|
495
642
|
const manifestPath = path.join(packageRoot, 'dist', 'skills-manifest.json');
|
|
496
643
|
if (!existsSyncImpl(manifestPath)) {
|
|
497
|
-
return createCheck('skills-manifest', 'Skills + hooks integrity', 'warn', 'No skills-manifest.json found. This is normal for source checkouts — packaged installs ship the manifest.', 'Run `npm run build` to regenerate, or reinstall via `npm install -g @pcircle/memesh`.');
|
|
644
|
+
return createCheck('skills-manifest', 'Skills + hooks integrity', 'warn', 'No skills-manifest.json found. This is normal for source checkouts — packaged installs ship the manifest.', 'Run `npm run build` to regenerate, or reinstall via `npm install -g @pcircle/memesh`.', { code: 'skills-manifest.missing-dev' });
|
|
498
645
|
}
|
|
499
646
|
let manifest;
|
|
500
647
|
try {
|
|
501
648
|
manifest = JSON.parse(readFileSyncImpl(manifestPath, 'utf8'));
|
|
502
649
|
}
|
|
503
650
|
catch (err) {
|
|
504
|
-
return createCheck('skills-manifest', 'Skills + hooks integrity', 'fail', `skills-manifest.json is unreadable (${err instanceof Error ? err.message : 'parse error'}).`, 'Reinstall the package: `npm install -g @pcircle/memesh`. If the problem persists open an issue.');
|
|
651
|
+
return createCheck('skills-manifest', 'Skills + hooks integrity', 'fail', `skills-manifest.json is unreadable (${err instanceof Error ? err.message : 'parse error'}).`, 'Reinstall the package: `npm install -g @pcircle/memesh`. If the problem persists open an issue.', { code: 'skills-manifest.unreadable', params: { detail: err instanceof Error ? err.message : 'parse error' } });
|
|
505
652
|
}
|
|
506
653
|
const entries = manifest.entries ?? [];
|
|
507
654
|
if (entries.length === 0) {
|
|
508
|
-
return createCheck('skills-manifest', 'Skills + hooks integrity', 'fail', 'skills-manifest.json contains zero entries.', 'Reinstall the package: `npm install -g @pcircle/memesh`.');
|
|
655
|
+
return createCheck('skills-manifest', 'Skills + hooks integrity', 'fail', 'skills-manifest.json contains zero entries.', 'Reinstall the package: `npm install -g @pcircle/memesh`.', { code: 'skills-manifest.empty' });
|
|
509
656
|
}
|
|
510
657
|
const mismatches = [];
|
|
511
658
|
const missing = [];
|
|
@@ -534,7 +681,7 @@ function verifySkillsManifest(packageRoot, existsSyncImpl, readFileSyncImpl) {
|
|
|
534
681
|
missing.length > 0 ? `${missing.length} missing: ${missing.slice(0, 3).join(', ')}${missing.length > 3 ? ` (+${missing.length - 3} more)` : ''}` : null,
|
|
535
682
|
mismatches.length > 0 ? `${mismatches.length} tampered: ${mismatches.slice(0, 3).join(', ')}${mismatches.length > 3 ? ` (+${mismatches.length - 3} more)` : ''}` : null,
|
|
536
683
|
].filter(Boolean).join('; ');
|
|
537
|
-
return createCheck('skills-manifest', 'Skills + hooks integrity', 'fail', `Manifest verification failed: ${detail}.`, 'Reinstall the package: `npm install -g @pcircle/memesh`. If the problem reproduces on a fresh install, open a security issue at https://github.com/PCIRCLE-AI/memesh-llm-memory/security.');
|
|
684
|
+
return createCheck('skills-manifest', 'Skills + hooks integrity', 'fail', `Manifest verification failed: ${detail}.`, 'Reinstall the package: `npm install -g @pcircle/memesh`. If the problem reproduces on a fresh install, open a security issue at https://github.com/PCIRCLE-AI/memesh-llm-memory/security.', { code: 'skills-manifest.verify-failed', params: { detail } });
|
|
538
685
|
}
|
|
539
686
|
async function inspectConfigParse(getConfigPathImpl, existsSyncImpl, readFileSyncImpl) {
|
|
540
687
|
const configPath = getConfigPathImpl();
|
|
@@ -545,13 +692,13 @@ async function inspectConfigParse(getConfigPathImpl, existsSyncImpl, readFileSyn
|
|
|
545
692
|
const raw = readFileSyncImpl(configPath, 'utf8');
|
|
546
693
|
const parsed = JSON.parse(raw);
|
|
547
694
|
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
548
|
-
return createCheck('config_parse', 'Config parses', 'fail', `${configPath} parsed but is not a JSON object — every setting is being ignored.`, `Fix or remove ${configPath}, then re-run memesh doctor
|
|
695
|
+
return createCheck('config_parse', 'Config parses', '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 } });
|
|
549
696
|
}
|
|
550
697
|
return createCheck('config_parse', 'Config parses', 'pass', `${configPath} is valid JSON and its settings are in effect.`);
|
|
551
698
|
}
|
|
552
699
|
catch (err) {
|
|
553
700
|
const msg = err instanceof Error ? err.message : String(err);
|
|
554
|
-
return createCheck('config_parse', 'Config parses', '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
|
|
701
|
+
return createCheck('config_parse', 'Config parses', '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 } });
|
|
555
702
|
}
|
|
556
703
|
}
|
|
557
704
|
async function inspectEmbeddingProbe(capabilities, probeCapabilities, embedTextImpl) {
|
|
@@ -559,13 +706,7 @@ async function inspectEmbeddingProbe(capabilities, probeCapabilities, embedTextI
|
|
|
559
706
|
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.');
|
|
560
707
|
}
|
|
561
708
|
if (!probeCapabilities) {
|
|
562
|
-
|
|
563
|
-
if (!isLocal) {
|
|
564
|
-
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)');
|
|
565
|
-
}
|
|
566
|
-
if (!isOnnxModelCached()) {
|
|
567
|
-
return createInfo('embeddings_probe', 'Embeddings work', `NOT VERIFIED. Config names "onnx" but the model is not in the local cache yet, and probing would download ~90 MB — which a diagnostic command must not do on its own.`, 'Run: memesh doctor --probe (downloads the model once, then verifies)');
|
|
568
|
-
}
|
|
709
|
+
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)');
|
|
569
710
|
}
|
|
570
711
|
let timer;
|
|
571
712
|
try {
|
|
@@ -576,13 +717,13 @@ async function inspectEmbeddingProbe(capabilities, probeCapabilities, embedTextI
|
|
|
576
717
|
}),
|
|
577
718
|
]);
|
|
578
719
|
if (!vector || vector.length === 0) {
|
|
579
|
-
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.');
|
|
720
|
+
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) } });
|
|
580
721
|
}
|
|
581
722
|
return createCheck('embeddings_probe', 'Embeddings work', 'pass', `Generated a ${vector.length}-dim test embedding via "${capabilities.embeddings}".`);
|
|
582
723
|
}
|
|
583
724
|
catch (err) {
|
|
584
725
|
const msg = err instanceof Error ? err.message : String(err);
|
|
585
|
-
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, or
|
|
726
|
+
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 } });
|
|
586
727
|
}
|
|
587
728
|
finally {
|
|
588
729
|
clearTimeout(timer);
|
|
@@ -601,11 +742,11 @@ async function inspectLlmProbe(capabilities, probeCapabilities, probeProviderImp
|
|
|
601
742
|
if (result.valid) {
|
|
602
743
|
return createCheck('llm_probe', 'LLM reachable', 'pass', `${llm.provider} answered a live probe.`);
|
|
603
744
|
}
|
|
604
|
-
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');
|
|
745
|
+
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' } });
|
|
605
746
|
}
|
|
606
747
|
catch (err) {
|
|
607
748
|
const msg = err instanceof Error ? err.message : String(err);
|
|
608
|
-
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');
|
|
749
|
+
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 } });
|
|
609
750
|
}
|
|
610
751
|
}
|
|
611
752
|
function summarizeOverallStatus(checks) {
|
|
@@ -627,7 +768,7 @@ export async function runDoctor(options) {
|
|
|
627
768
|
const installSupport = getInstallChannelSupportImpl(install);
|
|
628
769
|
checks.push(createCheck('install-channel', 'Install method', install === 'unknown' ? 'warn' : 'pass', `Install method detected: ${installSupport.label}.`, install === 'unknown'
|
|
629
770
|
? 'If this is a source checkout, run MeMesh from the repo root. If this is a packaged install, reinstall with `npm install -g @pcircle/memesh`.'
|
|
630
|
-
: undefined));
|
|
771
|
+
: undefined, install === 'unknown' ? { code: 'install-channel.unknown' } : undefined));
|
|
631
772
|
const databasePath = resolveDatabasePath();
|
|
632
773
|
const dbChecks = [];
|
|
633
774
|
try {
|
|
@@ -647,12 +788,12 @@ export async function runDoctor(options) {
|
|
|
647
788
|
dbChecks.push(createCheck('fts_segmentation', 'Keyword index segmentation', 'warn', `The keyword index holds ${unsegmented.c} unsegmented term(s), so some memories are only ` +
|
|
648
789
|
`findable by their exact full text. This happens when an older build wrote to a database ` +
|
|
649
790
|
`that a newer one had already migrated — the version marker only moves forward, so the ` +
|
|
650
|
-
`automatic rebuild cannot notice. Re-run doctor after the rebuild: this count should be 0.`, `Run 'memesh reindex --fts' to rebuild the keyword index
|
|
791
|
+
`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 } }));
|
|
651
792
|
}
|
|
652
793
|
}
|
|
653
794
|
const pendingReindex = getPendingReindexInfo();
|
|
654
795
|
if (pendingReindex) {
|
|
655
|
-
dbChecks.push(createCheck('vector_index', 'Vector Index', 'warn', `Search index needs rebuilding (embedding configuration changed)`, `Run 'memesh reindex' to fix. This will restore full search functionality
|
|
796
|
+
dbChecks.push(createCheck('vector_index', 'Vector Index', 'warn', `Search index needs rebuilding (embedding configuration changed)`, `Run 'memesh reindex' to fix. This will restore full search functionality.`, { code: 'vector-index.stale' }));
|
|
656
797
|
}
|
|
657
798
|
}
|
|
658
799
|
catch (err) {
|
|
@@ -708,7 +849,7 @@ export async function runDoctor(options) {
|
|
|
708
849
|
}
|
|
709
850
|
}
|
|
710
851
|
dbChecks.length = 0;
|
|
711
|
-
dbChecks.push(createCheck('database', 'Database', 'fail', diagnosis, fix));
|
|
852
|
+
dbChecks.push(createCheck('database', 'Database', 'fail', diagnosis, fix, { code: 'database.broken', params: { detail: diagnosis } }));
|
|
712
853
|
}
|
|
713
854
|
finally {
|
|
714
855
|
checks.push(...dbChecks);
|
|
@@ -721,8 +862,11 @@ export async function runDoctor(options) {
|
|
|
721
862
|
checks.push(inspectConfigFile(existsSyncImpl, readFileSyncImpl, getConfigPathImpl));
|
|
722
863
|
checks.push(inspectMcpConfig(packageRoot, existsSyncImpl, readFileSyncImpl));
|
|
723
864
|
checks.push(...inspectHooksConfig(packageRoot, platform, existsSyncImpl, readFileSyncImpl, statSyncImpl));
|
|
724
|
-
|
|
725
|
-
checks.push(
|
|
865
|
+
const wiring = inspectHookWiring(existsSyncImpl, readFileSyncImpl, memeshDir(), install);
|
|
866
|
+
checks.push(wiring);
|
|
867
|
+
const captureWired = wiring.status === 'pass'
|
|
868
|
+
&& (wiring.params === undefined || wiring.params.captureWired === 1);
|
|
869
|
+
checks.push(inspectHookActivity(openDatabaseImpl, safeCloseDatabaseImpl, existsSyncImpl, statSyncImpl, captureWired));
|
|
726
870
|
checks.push(inspectDashboardArtifact(packageRoot, existsSyncImpl));
|
|
727
871
|
checks.push(inspectNodeRuntime(packageRoot, existsSyncImpl, readFileSyncImpl));
|
|
728
872
|
checks.push(inspectNativeBinding(packageRoot, existsSyncImpl, nativeBindingProbeImpl));
|
|
@@ -730,6 +874,16 @@ export async function runDoctor(options) {
|
|
|
730
874
|
checks.push(verifySkillsManifest(packageRoot, existsSyncImpl, readFileSyncImpl));
|
|
731
875
|
const capabilities = detectCapabilitiesImpl();
|
|
732
876
|
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.`));
|
|
877
|
+
if (!isTranscriptMiningEnabled()) {
|
|
878
|
+
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`.'));
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
const last = lastTranscriptMineAt(getProjectName(process.cwd()));
|
|
882
|
+
const when = last === null
|
|
883
|
+
? 'not yet run for this project'
|
|
884
|
+
: `last mined ${((Date.now() - last) / 3600_000).toFixed(1)}h ago`;
|
|
885
|
+
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\`.`));
|
|
886
|
+
}
|
|
733
887
|
checks.push(await inspectConfigParse(getConfigPathImpl, existsSyncImpl, readFileSyncImpl));
|
|
734
888
|
checks.push(await inspectEmbeddingProbe(capabilities, probeCapabilities, embedTextImpl));
|
|
735
889
|
checks.push(await inspectLlmProbe(capabilities, probeCapabilities, probeProviderImpl));
|