@pcircle/memesh 2.9.0 → 2.9.2

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.
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SubagentStop Hook - Capture Subagent Results to MeMesh Knowledge Graph
5
+ *
6
+ * Triggered when a subagent finishes execution.
7
+ *
8
+ * Features:
9
+ * - Saves code review findings to MeMesh KG (high-value results)
10
+ * - Tracks code review completion for pre-commit enforcement
11
+ * - Updates session state with subagent activity
12
+ * - Silent operation (no console output)
13
+ */
14
+
15
+ import {
16
+ STATE_DIR,
17
+ MEMESH_DB_PATH,
18
+ readJSONFile,
19
+ writeJSONFile,
20
+ sqliteBatchEntity,
21
+ getDateString,
22
+ readStdin,
23
+ logError,
24
+ logMemorySave,
25
+ } from './hook-utils.js';
26
+ import fs from 'fs';
27
+ import path from 'path';
28
+
29
+ // ============================================================================
30
+ // Constants
31
+ // ============================================================================
32
+
33
+ const CURRENT_SESSION_FILE = path.join(STATE_DIR, 'current-session.json');
34
+
35
+ /** Agent types that count as code review */
36
+ const CODE_REVIEWER_TYPES = [
37
+ 'code-reviewer',
38
+ 'code-review',
39
+ 'superpowers:code-reviewer',
40
+ 'pr-review-toolkit:code-reviewer',
41
+ 'feature-dev:code-reviewer',
42
+ ];
43
+
44
+ // ============================================================================
45
+ // Code Review Detection
46
+ // ============================================================================
47
+
48
+ /**
49
+ * Check if this subagent is a code reviewer
50
+ * @param {string} agentType - Subagent type identifier
51
+ * @returns {boolean}
52
+ */
53
+ function isCodeReviewer(agentType) {
54
+ if (!agentType) return false;
55
+ const lower = agentType.toLowerCase();
56
+ return CODE_REVIEWER_TYPES.some(t => lower.includes(t.toLowerCase()));
57
+ }
58
+
59
+ /**
60
+ * Mark code review as done in session state.
61
+ * This flag is checked by pre-tool-use.js before git commits.
62
+ */
63
+ function markCodeReviewDone() {
64
+ const session = readJSONFile(CURRENT_SESSION_FILE, {});
65
+ session.codeReviewDone = true;
66
+ session.codeReviewTimestamp = new Date().toISOString();
67
+ writeJSONFile(CURRENT_SESSION_FILE, session);
68
+ }
69
+
70
+ // ============================================================================
71
+ // MeMesh KG Save
72
+ // ============================================================================
73
+
74
+ /**
75
+ * Save code review results to MeMesh knowledge graph.
76
+ * Only saves code reviewer subagent results (high-value findings).
77
+ *
78
+ * @param {string} agentType - Subagent type
79
+ * @param {string} lastMessage - Agent's final response
80
+ * @returns {boolean} True if saved
81
+ */
82
+ function saveSubagentToKG(agentType, lastMessage) {
83
+ try {
84
+ if (!fs.existsSync(MEMESH_DB_PATH)) return false;
85
+ if (!lastMessage || lastMessage.length < 50) return false;
86
+
87
+ // Truncate very long messages
88
+ const shortMessage = lastMessage.length > 1000
89
+ ? lastMessage.substring(0, 1000) + '...'
90
+ : lastMessage;
91
+
92
+ const entityName = `Code Review: ${getDateString()} ${Date.now()} ${agentType}`;
93
+ const metadata = JSON.stringify({
94
+ agentType,
95
+ messageLength: lastMessage.length,
96
+ source: 'subagent-stop-hook',
97
+ });
98
+
99
+ const tags = [
100
+ 'code-review',
101
+ `agent:${agentType}`,
102
+ `date:${getDateString()}`,
103
+ 'auto-tracked',
104
+ 'scope:project',
105
+ ];
106
+
107
+ const entityId = sqliteBatchEntity(
108
+ MEMESH_DB_PATH,
109
+ { name: entityName, type: 'code_review', metadata },
110
+ [`[${agentType}] ${shortMessage}`],
111
+ tags
112
+ );
113
+
114
+ if (entityId === null) return false;
115
+
116
+ logMemorySave(`Code review saved: ${agentType} (${lastMessage.length} chars)`);
117
+ return true;
118
+ } catch (error) {
119
+ logError('saveSubagentToKG', error);
120
+ return false;
121
+ }
122
+ }
123
+
124
+ // ============================================================================
125
+ // Session State Update
126
+ // ============================================================================
127
+
128
+ /**
129
+ * Track subagent activity in session state
130
+ * @param {string} agentType - Subagent type
131
+ */
132
+ function trackSubagentInSession(agentType) {
133
+ const session = readJSONFile(CURRENT_SESSION_FILE, { subagents: [] });
134
+ if (!session.subagents) session.subagents = [];
135
+
136
+ session.subagents.push({
137
+ type: agentType,
138
+ completedAt: new Date().toISOString(),
139
+ });
140
+
141
+ // Keep only last 20 subagent entries
142
+ if (session.subagents.length > 20) {
143
+ session.subagents = session.subagents.slice(-20);
144
+ }
145
+
146
+ writeJSONFile(CURRENT_SESSION_FILE, session);
147
+ }
148
+
149
+ // ============================================================================
150
+ // Main
151
+ // ============================================================================
152
+
153
+ async function subagentStop() {
154
+ try {
155
+ const input = await readStdin(3000);
156
+ if (!input || input.trim() === '') {
157
+ process.exit(0);
158
+ }
159
+
160
+ const data = JSON.parse(input);
161
+ const agentType = data.agent_type || data.agentType || 'unknown';
162
+ const lastMessage = data.last_assistant_message || data.lastAssistantMessage || '';
163
+
164
+ // Track all subagent completions in session state
165
+ trackSubagentInSession(agentType);
166
+
167
+ // Track code review completion (for pre-commit enforcement)
168
+ if (isCodeReviewer(agentType)) {
169
+ markCodeReviewDone();
170
+ }
171
+
172
+ // Save code reviewer results to MeMesh KG (high-value findings)
173
+ if (isCodeReviewer(agentType) && lastMessage.length > 50) {
174
+ saveSubagentToKG(agentType, lastMessage);
175
+ }
176
+
177
+ process.exit(0);
178
+ } catch (error) {
179
+ logError('SubagentStop', error);
180
+ process.exit(0); // Never block on hook errors
181
+ }
182
+ }
183
+
184
+ subagentStop();
@@ -203,31 +203,35 @@ export async function ensurePluginEnabled(claudeDir = join(homedir(), '.claude')
203
203
  */
204
204
  export async function ensureMCPConfigured(installPath, mode, claudeDir = join(homedir(), '.claude')) {
205
205
  const mcpSettingsFile = join(claudeDir, 'mcp_settings.json');
206
+
206
207
  // Read existing config or create new
207
208
  let config = readJSONFile(mcpSettingsFile) || { mcpServers: {} };
208
209
  if (!config.mcpServers) {
209
210
  config.mcpServers = {};
210
211
  }
212
+
211
213
  // Configure memesh entry based on mode
212
214
  if (mode === 'global') {
215
+ // Global install: use npx (always uses latest published version)
213
216
  config.mcpServers.memesh = {
214
217
  command: 'npx',
215
218
  args: ['-y', '@pcircle/memesh'],
216
219
  env: { NODE_ENV: 'production' }
217
220
  };
218
- }
219
- else {
221
+ } else {
222
+ // Local dev: use node + absolute path (for testing)
220
223
  const serverPath = join(installPath, 'dist', 'mcp', 'server-bootstrap.js');
221
224
  config.mcpServers.memesh = {
222
225
  command: 'node',
223
- args: [serverPath],
224
- env: { NODE_ENV: 'production' }
226
+ args: [serverPath]
225
227
  };
226
228
  }
229
+
227
230
  // Remove legacy claude-code-buddy entry if exists
228
231
  if (config.mcpServers['claude-code-buddy']) {
229
232
  delete config.mcpServers['claude-code-buddy'];
230
233
  }
234
+
231
235
  // Write back
232
236
  writeJSONFile(mcpSettingsFile, config);
233
237
  }
@@ -173,13 +173,21 @@ async function main() {
173
173
  }
174
174
 
175
175
  // Step 5: MCP Configuration
176
- try {
177
- await ensureMCPConfigured(installPath, mode, claudeDir);
178
- results.mcpConfigured = true;
179
- console.log(chalk.green(' MCP configured'));
180
- } catch (error) {
181
- results.errors.push(`MCP: ${error.message}`);
182
- console.log(chalk.yellow(` ⚠️ MCP configuration failed (non-fatal)`));
176
+ // IMPORTANT: Skip MCP config for local dev to prevent path pollution
177
+ // (see v2.9.0 Issue 2: Local Development Path Pollution)
178
+ if (mode === 'local') {
179
+ console.log(chalk.yellow(' ⚠️ Skipping MCP configuration (local development mode)'));
180
+ console.log(chalk.gray(' This prevents writing local dev paths to ~/.claude/mcp_settings.json'));
181
+ results.mcpConfigured = false;
182
+ } else {
183
+ try {
184
+ await ensureMCPConfigured(installPath, mode, claudeDir);
185
+ results.mcpConfigured = true;
186
+ console.log(chalk.green(' ✅ MCP configured'));
187
+ } catch (error) {
188
+ results.errors.push(`MCP: ${error.message}`);
189
+ console.log(chalk.yellow(` ⚠️ MCP configuration failed (non-fatal)`));
190
+ }
183
191
  }
184
192
 
185
193
  // Step 6: Legacy Installation Fix