@pcircle/memesh 2.10.0 → 2.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pcircle/memesh",
3
- "version": "2.10.0",
3
+ "version": "2.10.1",
4
4
  "description": "MeMesh — Persistent memory plugin for Claude Code. Remembers architecture decisions, coding patterns, and project context across sessions.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -9,14 +9,13 @@
9
9
  },
10
10
  "files": [
11
11
  "dist/",
12
- ".claude-plugin/plugin.json",
13
12
  ".mcp.json",
14
13
  "hooks/hooks.json",
15
- "skills/",
16
14
  "scripts/postinstall-new.js",
17
15
  "scripts/postinstall-lib.js",
18
16
  "scripts/health-check.js",
19
17
  "scripts/hooks/",
18
+ "scripts/skills/",
20
19
  "plugin.json",
21
20
  "README.md",
22
21
  "README.zh-TW.md",
package/plugin.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "PCIRCLE AI"
6
6
  },
7
- "version": "2.10.0",
7
+ "version": "2.10.1",
8
8
  "homepage": "https://github.com/PCIRCLE-AI/claude-code-buddy",
9
9
  "repository": "https://github.com/PCIRCLE-AI/claude-code-buddy",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  * MeMesh Plugin Health Check
5
5
  *
6
6
  * Fast, non-invasive validation of plugin installation.
7
- * Checks all 4 critical paths and reports issues without modifying anything.
7
+ * Supports both npm global install and local dev install.
8
8
  *
9
9
  * Exit codes:
10
10
  * 0 - All healthy
@@ -26,19 +26,47 @@ const silent = process.argv.includes('--silent');
26
26
  const verbose = process.argv.includes('--verbose');
27
27
  const json = process.argv.includes('--json');
28
28
 
29
+ /**
30
+ * Detect installation mode based on directory structure.
31
+ * - 'npm-global': installed via npm install -g (dist/ is in package root)
32
+ * - 'dev': running from project source (needs .claude-plugin/memesh/dist/)
33
+ * - 'plugin': installed via /plugin marketplace add (managed by Claude Code)
34
+ */
35
+ function detectInstallMode() {
36
+ // If dist/mcp/server-bootstrap.js exists at package root, it's npm or plugin install
37
+ if (existsSync(join(projectRoot, 'dist', 'mcp', 'server-bootstrap.js'))) {
38
+ // Check if we're inside node_modules (npm global)
39
+ if (projectRoot.includes('node_modules')) {
40
+ return 'npm-global';
41
+ }
42
+ // Check if src/ exists (dev environment)
43
+ if (existsSync(join(projectRoot, 'src'))) {
44
+ return 'dev';
45
+ }
46
+ return 'plugin';
47
+ }
48
+ // Fallback: dev mode without build
49
+ return 'dev';
50
+ }
51
+
52
+ const installMode = detectInstallMode();
53
+
29
54
  /**
30
55
  * Health check result structure
31
56
  */
32
57
  const result = {
33
58
  healthy: true,
59
+ installMode,
34
60
  issues: [],
35
61
  timestamp: new Date().toISOString(),
36
62
  checks: {
37
63
  dist: false,
64
+ pluginJson: false,
65
+ mcpJson: false,
66
+ hooks: false,
38
67
  marketplace: false,
39
68
  symlink: false,
40
69
  settings: false,
41
- mcp: false
42
70
  }
43
71
  };
44
72
 
@@ -58,95 +86,181 @@ function addIssue(path, severity, message, repairable = true) {
58
86
  * Log success message
59
87
  */
60
88
  function logSuccess(message) {
61
- if (!silent && !json && verbose) {
62
- console.log(` ✅ ${message}`);
89
+ if (!silent && !json) {
90
+ if (verbose) {
91
+ console.log(` ✅ ${message}`);
92
+ }
63
93
  }
64
94
  }
65
95
 
66
96
  // ============================================================================
67
- // Check 1: Dist directory exists (required for all other checks)
97
+ // Start
68
98
  // ============================================================================
69
99
 
70
- if (!silent && !json) console.log('🔍 Checking MeMesh plugin installation...\n');
100
+ if (!silent && !json) {
101
+ console.log('🔍 Checking MeMesh Plugin installation...\n');
102
+ console.log(` Mode: ${installMode}`);
103
+ console.log(` Path: ${projectRoot}\n`);
104
+ }
71
105
 
72
- const distPath = join(projectRoot, '.claude-plugin', 'memesh', 'dist', 'mcp', 'server-bootstrap.js');
106
+ // ============================================================================
107
+ // Check 1: Server bootstrap exists
108
+ // ============================================================================
73
109
 
74
- if (!existsSync(distPath)) {
75
- addIssue('dist', 'error', 'Plugin not built (.claude-plugin/memesh/dist/ missing)', false);
76
- result.checks.dist = false;
110
+ const serverPath = installMode === 'dev'
111
+ ? join(projectRoot, '.claude-plugin', 'memesh', 'dist', 'mcp', 'server-bootstrap.js')
112
+ : join(projectRoot, 'dist', 'mcp', 'server-bootstrap.js');
113
+
114
+ if (!existsSync(serverPath)) {
115
+ const hint = installMode === 'dev' ? 'Run: npm run build' : 'Reinstall: npm install -g @pcircle/memesh';
116
+ addIssue('dist', 'error', `server-bootstrap.js not found at ${serverPath}`, false);
77
117
 
78
118
  if (!silent && !json) {
79
- console.error('\n❌ Plugin not built. Run: npm run build\n');
119
+ console.error(`\n❌ Plugin not built. ${hint}\n`);
80
120
  }
81
121
 
82
122
  if (json) {
83
123
  console.log(JSON.stringify(result, null, 2));
84
124
  }
85
125
 
86
- process.exit(2); // Fatal error
126
+ process.exit(2);
87
127
  } else {
88
128
  result.checks.dist = true;
89
- logSuccess('Plugin dist/ exists');
129
+ logSuccess('server-bootstrap.js exists');
130
+ }
131
+
132
+ // ============================================================================
133
+ // Check 2: plugin.json exists and is valid
134
+ // ============================================================================
135
+
136
+ const pluginJsonPath = join(projectRoot, 'plugin.json');
137
+
138
+ try {
139
+ if (!existsSync(pluginJsonPath)) {
140
+ addIssue('pluginJson', 'error', 'plugin.json not found');
141
+ } else {
142
+ const plugin = JSON.parse(readFileSync(pluginJsonPath, 'utf-8'));
143
+ if (!plugin.name || !plugin.version) {
144
+ addIssue('pluginJson', 'error', 'plugin.json missing name or version');
145
+ } else {
146
+ result.checks.pluginJson = true;
147
+ logSuccess(`plugin.json valid (${plugin.name} v${plugin.version})`);
148
+ }
149
+ }
150
+ } catch (error) {
151
+ addIssue('pluginJson', 'error', `Failed to parse plugin.json: ${error.message}`);
152
+ }
153
+
154
+ // ============================================================================
155
+ // Check 3: .mcp.json exists and is valid
156
+ // ============================================================================
157
+
158
+ const mcpJsonPath = join(projectRoot, '.mcp.json');
159
+
160
+ try {
161
+ if (!existsSync(mcpJsonPath)) {
162
+ addIssue('mcpJson', 'error', '.mcp.json not found');
163
+ } else {
164
+ const mcp = JSON.parse(readFileSync(mcpJsonPath, 'utf-8'));
165
+ if (!mcp.mcpServers?.memesh) {
166
+ addIssue('mcpJson', 'error', '.mcp.json missing memesh server definition');
167
+ } else {
168
+ result.checks.mcpJson = true;
169
+ logSuccess('.mcp.json valid');
170
+ }
171
+ }
172
+ } catch (error) {
173
+ addIssue('mcpJson', 'error', `Failed to parse .mcp.json: ${error.message}`);
174
+ }
175
+
176
+ // ============================================================================
177
+ // Check 4: hooks/hooks.json exists and scripts are present
178
+ // ============================================================================
179
+
180
+ const hooksJsonPath = join(projectRoot, 'hooks', 'hooks.json');
181
+
182
+ try {
183
+ if (!existsSync(hooksJsonPath)) {
184
+ addIssue('hooks', 'error', 'hooks/hooks.json not found');
185
+ } else {
186
+ const hooksConfig = JSON.parse(readFileSync(hooksJsonPath, 'utf-8'));
187
+ const events = Object.keys(hooksConfig.hooks || {});
188
+
189
+ if (events.length === 0) {
190
+ addIssue('hooks', 'warning', 'No hook events defined');
191
+ } else {
192
+ let allScriptsExist = true;
193
+ for (const event of events) {
194
+ for (const entry of hooksConfig.hooks[event]) {
195
+ for (const hook of entry.hooks) {
196
+ const scriptPath = hook.command.replace('${CLAUDE_PLUGIN_ROOT}', projectRoot);
197
+ if (!existsSync(scriptPath)) {
198
+ addIssue('hooks', 'error', `Hook script missing: ${scriptPath}`);
199
+ allScriptsExist = false;
200
+ }
201
+ }
202
+ }
203
+ }
204
+ if (allScriptsExist) {
205
+ result.checks.hooks = true;
206
+ logSuccess(`hooks valid (${events.length} events)`);
207
+ }
208
+ }
209
+ }
210
+ } catch (error) {
211
+ addIssue('hooks', 'error', `Failed to parse hooks.json: ${error.message}`);
90
212
  }
91
213
 
92
214
  // ============================================================================
93
- // Check 2: Marketplace registration
215
+ // Check 5: Marketplace registration (npm-global and dev modes)
94
216
  // ============================================================================
95
217
 
96
218
  const knownMarketplacesPath = join(homedir(), '.claude', 'plugins', 'known_marketplaces.json');
97
- const claudePluginRoot = join(projectRoot, '.claude-plugin');
98
219
 
99
220
  try {
100
221
  if (!existsSync(knownMarketplacesPath)) {
101
- addIssue('marketplace', 'error', 'known_marketplaces.json not found');
222
+ addIssue('marketplace', 'warning', 'known_marketplaces.json not found (plugin may be installed via /plugin command)');
102
223
  } else {
103
224
  const content = readFileSync(knownMarketplacesPath, 'utf-8');
104
225
  const marketplaces = JSON.parse(content);
105
226
 
106
- if (!marketplaces['pcircle-ai']) {
107
- addIssue('marketplace', 'error', 'pcircle-ai marketplace not registered');
227
+ if (marketplaces['pcircle-ai']) {
228
+ result.checks.marketplace = true;
229
+ logSuccess('Marketplace registered');
108
230
  } else {
109
- const entry = marketplaces['pcircle-ai'];
110
- const expectedPath = claudePluginRoot;
111
-
112
- if (entry.source?.path !== expectedPath) {
113
- addIssue('marketplace', 'warning', `Marketplace path incorrect (expected: ${expectedPath}, got: ${entry.source?.path})`);
114
- } else {
115
- result.checks.marketplace = true;
116
- logSuccess('Marketplace registered correctly');
117
- }
231
+ addIssue('marketplace', 'warning', 'pcircle-ai not in known_marketplaces.json (may be installed via /plugin command)');
118
232
  }
119
233
  }
120
234
  } catch (error) {
121
- addIssue('marketplace', 'error', `Failed to parse known_marketplaces.json: ${error.message}`);
235
+ addIssue('marketplace', 'error', `Failed to check marketplace: ${error.message}`);
122
236
  }
123
237
 
124
238
  // ============================================================================
125
- // Check 3: Symlink validity
239
+ // Check 6: Symlink or plugin discovery
126
240
  // ============================================================================
127
241
 
128
242
  const symlinkPath = join(homedir(), '.claude', 'plugins', 'marketplaces', 'pcircle-ai');
129
243
 
130
244
  try {
131
245
  if (!existsSync(symlinkPath)) {
132
- addIssue('symlink', 'error', 'Marketplace symlink not found');
246
+ addIssue('symlink', 'warning', 'Marketplace symlink not found (may be installed via /plugin command)');
133
247
  } else {
134
248
  const stats = lstatSync(symlinkPath);
135
249
 
136
- if (!stats.isSymbolicLink()) {
137
- addIssue('symlink', 'error', 'Marketplace path is not a symlink');
138
- } else {
250
+ if (stats.isSymbolicLink()) {
139
251
  const target = realpathSync(symlinkPath);
140
- const expectedTarget = realpathSync(claudePluginRoot);
141
-
142
- if (target !== expectedTarget) {
143
- addIssue('symlink', 'warning', `Symlink points to wrong location (expected: ${expectedTarget}, got: ${target})`);
144
- } else if (!existsSync(target)) {
145
- addIssue('symlink', 'error', 'Symlink target does not exist');
252
+ if (!existsSync(target)) {
253
+ addIssue('symlink', 'error', 'Symlink target does not exist (broken symlink)');
146
254
  } else {
147
255
  result.checks.symlink = true;
148
- logSuccess('Symlink valid');
256
+ logSuccess(`Symlink valid → ${target}`);
149
257
  }
258
+ } else if (stats.isDirectory()) {
259
+ // Could be a direct clone (plugin marketplace install)
260
+ result.checks.symlink = true;
261
+ logSuccess('Plugin directory exists (marketplace install)');
262
+ } else {
263
+ addIssue('symlink', 'error', 'Marketplace path is not a symlink or directory');
150
264
  }
151
265
  }
152
266
  } catch (error) {
@@ -154,22 +268,22 @@ try {
154
268
  }
155
269
 
156
270
  // ============================================================================
157
- // Check 4: Plugin enabled in settings
271
+ // Check 7: Plugin enabled in settings
158
272
  // ============================================================================
159
273
 
160
274
  const settingsPath = join(homedir(), '.claude', 'settings.json');
161
275
 
162
276
  try {
163
277
  if (!existsSync(settingsPath)) {
164
- addIssue('settings', 'error', 'settings.json not found');
278
+ addIssue('settings', 'warning', 'settings.json not found');
165
279
  } else {
166
280
  const content = readFileSync(settingsPath, 'utf-8');
167
281
  const settings = JSON.parse(content);
168
282
 
169
283
  if (!settings.enabledPlugins) {
170
- addIssue('settings', 'error', 'enabledPlugins object missing');
284
+ addIssue('settings', 'warning', 'enabledPlugins not found in settings.json');
171
285
  } else if (!settings.enabledPlugins['memesh@pcircle-ai']) {
172
- addIssue('settings', 'error', 'memesh@pcircle-ai not enabled');
286
+ addIssue('settings', 'warning', 'memesh@pcircle-ai not enabled (may need to enable via /plugin command)');
173
287
  } else if (settings.enabledPlugins['memesh@pcircle-ai'] !== true) {
174
288
  addIssue('settings', 'warning', 'memesh@pcircle-ai is disabled');
175
289
  } else {
@@ -178,48 +292,7 @@ try {
178
292
  }
179
293
  }
180
294
  } catch (error) {
181
- addIssue('settings', 'error', `Failed to parse settings.json: ${error.message}`);
182
- }
183
-
184
- // ============================================================================
185
- // Check 5: MCP server configuration
186
- // ============================================================================
187
-
188
- const mcpSettingsPath = join(homedir(), '.claude', 'mcp_settings.json');
189
- const expectedMcpPath = distPath;
190
-
191
- try {
192
- if (!existsSync(mcpSettingsPath)) {
193
- addIssue('mcp', 'error', 'mcp_settings.json not found');
194
- } else {
195
- const content = readFileSync(mcpSettingsPath, 'utf-8');
196
- const mcpSettings = JSON.parse(content);
197
-
198
- if (!mcpSettings.mcpServers) {
199
- addIssue('mcp', 'error', 'mcpServers object missing');
200
- } else if (!mcpSettings.mcpServers.memesh) {
201
- addIssue('mcp', 'error', 'memesh server not configured');
202
- } else {
203
- const memeshConfig = mcpSettings.mcpServers.memesh;
204
-
205
- // Check command
206
- if (memeshConfig.command !== 'node') {
207
- addIssue('mcp', 'warning', 'MCP server command should be "node"');
208
- }
209
-
210
- // Check args
211
- if (!Array.isArray(memeshConfig.args) || memeshConfig.args.length === 0) {
212
- addIssue('mcp', 'error', 'MCP server args missing');
213
- } else if (memeshConfig.args[0] !== expectedMcpPath) {
214
- addIssue('mcp', 'warning', `MCP server path incorrect (expected: ${expectedMcpPath}, got: ${memeshConfig.args[0]})`);
215
- } else {
216
- result.checks.mcp = true;
217
- logSuccess('MCP server configured correctly');
218
- }
219
- }
220
- }
221
- } catch (error) {
222
- addIssue('mcp', 'error', `Failed to parse mcp_settings.json: ${error.message}`);
295
+ addIssue('settings', 'error', `Failed to check settings: ${error.message}`);
223
296
  }
224
297
 
225
298
  // ============================================================================
@@ -235,21 +308,24 @@ if (json) {
235
308
  console.log('✅ All checks passed - plugin installation healthy');
236
309
  console.log('═'.repeat(60));
237
310
  } else {
238
- const errorCount = result.issues.filter(i => i.severity === 'error').length;
239
- const warningCount = result.issues.filter(i => i.severity === 'warning').length;
240
- const repairableCount = result.issues.filter(i => i.repairable).length;
311
+ const errors = result.issues.filter(i => i.severity === 'error');
312
+ const warnings = result.issues.filter(i => i.severity === 'warning');
241
313
 
242
- console.log(`❌ Found ${result.issues.length} issue(s): ${errorCount} error(s), ${warningCount} warning(s)`);
314
+ if (errors.length > 0) {
315
+ console.log(`❌ Found ${errors.length} error(s), ${warnings.length} warning(s)`);
316
+ } else {
317
+ console.log(`⚠️ Found ${warnings.length} warning(s) (non-critical)`);
318
+ }
243
319
  console.log('═'.repeat(60));
244
320
 
245
- if (repairableCount > 0) {
246
- console.log(`\n🔧 ${repairableCount} issue(s) are auto-repairable. Run: npm run health:repair\n`);
247
- } else {
248
- console.log('\n⚠️ Issues require manual intervention. Run: npm run build\n');
321
+ if (errors.length > 0) {
322
+ const hint = installMode === 'dev' ? 'npm run build' : 'npm install -g @pcircle/memesh';
323
+ console.log(`\n🔧 Fix errors first. Try: ${hint}\n`);
249
324
  }
250
325
  }
251
326
  }
252
327
 
253
- // Exit with appropriate code
254
- const hasUnrepairableIssues = result.issues.some(i => !i.repairable);
255
- process.exit(result.healthy ? 0 : (hasUnrepairableIssues ? 2 : 1));
328
+ // Exit: errors 1 or 2, warnings only → 0
329
+ const hasErrors = result.issues.some(i => i.severity === 'error');
330
+ const hasUnrepairableErrors = result.issues.some(i => i.severity === 'error' && !i.repairable);
331
+ process.exit(hasErrors ? (hasUnrepairableErrors ? 2 : 1) : 0);
@@ -1,15 +0,0 @@
1
- {
2
- "name": "memesh",
3
- "description": "MeMesh — Persistent memory plugin for Claude Code. Remembers architecture decisions, coding patterns, and project context across sessions.",
4
- "author": {
5
- "name": "PCIRCLE AI"
6
- },
7
- "version": "2.10.0",
8
- "homepage": "https://github.com/PCIRCLE-AI/claude-code-buddy",
9
- "repository": "https://github.com/PCIRCLE-AI/claude-code-buddy",
10
- "license": "MIT",
11
- "keywords": ["claude-code", "mcp", "knowledge-graph", "ai-memory", "persistent-memory"],
12
- "mcpServers": "./.mcp.json",
13
- "hooks": "./hooks/hooks.json",
14
- "skills": "./skills/"
15
- }
@@ -1,46 +0,0 @@
1
- ## Required Plan Sections
2
-
3
- Your plan MUST include all of the following sections. Incomplete plans will be rejected.
4
-
5
- ### 1. System Design Description (SDD)
6
- - Component architecture and responsibilities
7
- - Data flow between components
8
- - Interface contracts (input/output types)
9
- - Dependencies and integration points
10
-
11
- ### 2. Behavior-Driven Design (BDD)
12
- For EACH feature, write Gherkin scenarios:
13
-
14
- ```gherkin
15
- Scenario: [descriptive name]
16
- Given [precondition]
17
- When [action]
18
- Then [expected outcome]
19
- ```
20
-
21
- Cover: happy path, error path, edge cases.
22
-
23
- ### 3. Edge Case Handling
24
-
25
- | Edge Case | How Handled | Test Coverage |
26
- |-----------|------------|---------------|
27
- | [case] | [strategy] | [yes/no] |
28
-
29
- Include at minimum: empty input, null/undefined, boundary values, concurrent access, timeout, large data.
30
-
31
- ### 4. Dry-Run Test Plan
32
- Before dispatching heavy tasks, verify:
33
- - [ ] Core function compiles (`tsc --noEmit` or `node --check`)
34
- - [ ] Unit test for critical path passes
35
- - [ ] Integration point verified with real call
36
- - [ ] No regressions in existing tests
37
-
38
- ### 5. Risk Assessment
39
-
40
- | Risk | Probability | Impact | Mitigation |
41
- |------|------------|--------|------------|
42
- | [risk] | High/Med/Low | High/Med/Low | [strategy] |
43
-
44
- ---
45
-
46
- **IMPORTANT**: After completing this plan, present it to the user and wait for explicit approval before proceeding to implementation. Do NOT auto-execute.