Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"name": "memesh",
"source": "./",
"description": "MeMesh — Local memory for Claude Code and MCP coding agents. One SQLite file, zero cloud required.",
"version": "4.2.6",
"version": "4.2.7",
"author": {
"name": "PCIRCLE AI"
},
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"author": {
"name": "PCIRCLE AI"
},
"version": "4.2.6",
"version": "4.2.7",
"homepage": "https://pcircle.ai/memesh-llm-memory",
"repository": "https://github.com/PCIRCLE-AI/memesh-llm-memory",
"license": "MIT",
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to MeMesh are documented here.

## [4.2.7] — 2026-05-13

### Added
- **`memesh doctor` Shell CLI check** (`src/core/doctor.ts`) — new check `Shell CLI on PATH` resolves `memesh` via the user's shell PATH (`which` / `where`) and detects the most common plugin-marketplace gotcha: plugin is installed (MCP + hooks + `/memesh` skill work) but `memesh` is NOT on the shell PATH, so typing `memesh reindex` in a terminal yields `command not found`. WARN on plugin-marketplace installs without a separate shell-PATH `memesh`, with the exact fix command (`npm install -g @pcircle/memesh`) and the clarification that both paths coexist and share the same DB. Informational PASS on `npm-global` (running from the install itself), `source-checkout` (informational only), and any plugin-marketplace install that already has a separate shell-PATH `memesh`. Mirrors the new "Install paths at a glance" section landed in v4.2.6 docs — users who hit the gotcha now get told by doctor instead of having to re-read the README.

## [4.2.6] — 2026-05-13

### Fixed
Expand Down
1 change: 1 addition & 0 deletions dist/core/doctor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface DoctorOptions {
ok: false;
message: string;
};
resolveShellMemeshImpl?: () => string | null;
}
export declare function runDoctor(options: DoctorOptions): Promise<DoctorResult>;
export declare function formatDoctorReport(result: DoctorResult, packageVersion: string): string[];
Expand Down
2 changes: 1 addition & 1 deletion dist/core/doctor.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 35 additions & 1 deletion dist/core/doctor.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/core/doctor.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/skills-manifest.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"schema": "memesh.skills-manifest/v1",
"generated_at": "2026-05-13T08:03:27.372Z",
"generated_at": "2026-05-13T08:21:37.569Z",
"entries": [
{
"path": ".claude-plugin/plugin.json",
"sha256": "34a93efe40e01daa689670b91ce9937c9cf018b6cf60de15bab89534625d215f",
"sha256": "22a438f9154be0174699ca1204a5ff3908c92bfef872538ab5a640a7bc665278",
"bytes": 441
},
{
Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MeMesh Plugin Architecture

**Version**: 4.2.6
**Version**: 4.2.7

---

Expand Down
2 changes: 1 addition & 1 deletion docs/api/API_REFERENCE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# MeMesh Plugin -- API Reference

**Protocol**: Model Context Protocol (MCP) over stdio
**Version**: 4.2.6
**Version**: 4.2.7
**Compatibility**: Works with Claude Code plugins, Claude Managed Agents (via MCP connector), and any MCP-compatible client.

---
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pcircle/memesh",
"version": "4.2.6",
"version": "4.2.7",
"description": "MeMesh — Local memory for Claude Code and MCP coding agents. One SQLite file, zero cloud required.",
"main": "dist/index.js",
"type": "module",
Expand Down
100 changes: 100 additions & 0 deletions src/core/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ interface DoctorOptions {
* hitting the real native module.
*/
nativeBindingProbeImpl?: (packageRoot: string) => { ok: true } | { ok: false; message: string };
/**
* Test seam: resolve `memesh` on the user's shell PATH. Default uses
* `which` / `where` via execFileSync. Returns the resolved absolute
* path or null when not found. Tests inject a stub.
*/
resolveShellMemeshImpl?: () => string | null;
}

const EXPECTED_HOOK_TYPES = ['PreToolUse', 'SessionStart', 'PostToolUse', 'Stop', 'PreCompact'];
Expand Down Expand Up @@ -643,6 +649,26 @@ function inspectHookActivity(
* skip-and-exits without writing entities. Without this check, the bug
* is invisible until a user notices "the dashboard is empty" days later.
*/
function defaultResolveShellMemesh(): string | null {
// Use `which` on POSIX and `where` on Windows. Both return the first
// matching binary on PATH. Return null on any failure (not found,
// command missing, permission error) — caller treats null as "no
// shell memesh available".
try {
const cmd = process.platform === 'win32' ? 'where' : 'which';
// node:child_process is already imported lazily by other doctor checks;
// re-use the createRequire chain so we don't add a top-level import
// just for this seam.
const localRequire = createRequire(import.meta.url);
const { execFileSync } = localRequire('child_process');
const out = execFileSync(cmd, ['memesh'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
const first = String(out).split(/\r?\n/).map((s) => s.trim()).find(Boolean);
return first || null;
} catch {
return null;
}
}

function defaultNativeBindingProbe(packageRoot: string): { ok: true } | { ok: false; message: string } {
// No test-env seam here. Earlier versions of this function gated on
// `process.env.VITEST === 'true'` to let test fixtures stub
Expand Down Expand Up @@ -723,6 +749,78 @@ function inspectNativeBinding(
);
}

/**
* Detect the "plugin without shell CLI" gotcha that confuses every new
* plugin-marketplace user. Symptom: `/plugin install memesh@pcircle-memesh`
* gives you MCP tools + hooks + the `/memesh` skill inside Claude Code,
* but does NOT put `memesh` on the shell `PATH`. Users then try
* `memesh reindex` in a terminal and see `command not found: memesh`.
*
* Resolution: also run `npm install -g @pcircle/memesh`. Both paths
* coexist and share the same DB.
*
* This check fires WARN only on plugin-marketplace installs that lack
* a separate shell-PATH `memesh`. For npm-global / source-checkout
* channels the check reports PASS with the resolved path. We don't
* gate it as FAIL because plugin-only is a valid setup for users who
* only ever interact with memesh through Claude Code chat.
*/
function inspectShellCli(
installChannel: import('./install-channel.js').InstallChannel,
packageRoot: string,
resolveShellMemeshImpl: () => string | null,
): DoctorCheck {
const shellPath = resolveShellMemeshImpl();
// Normalize and compare: a shell `memesh` that points back into the
// same install we're currently running from isn't a "separate" shell
// CLI — it's the same one. The common case where this matters is
// plugin-marketplace, where `which memesh` returns null and the user
// typed `memesh doctor` via some launcher / npx.
const isSameAsCurrent = shellPath ? path.resolve(shellPath).startsWith(path.resolve(packageRoot)) : false;
const hasDistinctShellCli = !!shellPath && !isSameAsCurrent;

if (installChannel === 'npm-global') {
return createCheck(
'shell-cli',
'Shell CLI on PATH',
'pass',
shellPath
? `\`memesh\` resolves to ${shellPath} (npm-global install — terminals across the machine pick it up).`
: 'Running from npm-global install — shell access available in this terminal.',
);
}

if (hasDistinctShellCli) {
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.`,
);
}

if (installChannel === 'plugin-marketplace') {
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`. '
+ '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`.',
);
}

// source-checkout / npm-local / unknown — informational only.
return createCheck(
'shell-cli',
'Shell CLI on PATH',
'pass',
shellPath
? `\`memesh\` resolves to ${shellPath}.`
: `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).`,
);
}

function inspectDashboardArtifact(
packageRoot: string,
existsSyncImpl: typeof fs.existsSync,
Expand Down Expand Up @@ -1041,6 +1139,7 @@ export async function runDoctor(options: DoctorOptions): Promise<DoctorResult> {
statSyncImpl = fs.statSync,
fetchImpl = fetch,
nativeBindingProbeImpl,
resolveShellMemeshImpl = defaultResolveShellMemesh,
} = options;

// F16: If the database is already open before doctor runs (e.g., the
Expand Down Expand Up @@ -1175,6 +1274,7 @@ export async function runDoctor(options: DoctorOptions): Promise<DoctorResult> {
checks.push(inspectHookActivity(openDatabaseImpl, safeCloseDatabaseImpl, existsSyncImpl, statSyncImpl));
checks.push(inspectDashboardArtifact(packageRoot, existsSyncImpl));
checks.push(inspectNativeBinding(packageRoot, existsSyncImpl, nativeBindingProbeImpl));
checks.push(inspectShellCli(install, packageRoot, resolveShellMemeshImpl));
checks.push(verifySkillsManifest(packageRoot, existsSyncImpl, readFileSyncImpl));

const capabilities = detectCapabilitiesImpl();
Expand Down
Loading
Loading