@equilateral_ai/mindmeld 3.3.0 → 3.4.0

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.
Files changed (69) hide show
  1. package/README.md +1 -10
  2. package/hooks/pre-compact.js +213 -25
  3. package/hooks/session-start.js +636 -42
  4. package/hooks/subagent-start.js +150 -0
  5. package/hooks/subagent-stop.js +184 -0
  6. package/package.json +8 -7
  7. package/scripts/init-project.js +74 -33
  8. package/scripts/mcp-bridge.js +220 -0
  9. package/src/core/CorrelationAnalyzer.js +157 -0
  10. package/src/core/LLMPatternDetector.js +198 -0
  11. package/src/core/RelevanceDetector.js +123 -36
  12. package/src/core/StandardsIngestion.js +119 -18
  13. package/src/handlers/activity/activityGetMe.js +1 -1
  14. package/src/handlers/activity/activityGetTeam.js +100 -55
  15. package/src/handlers/admin/adminSetup.js +216 -0
  16. package/src/handlers/alerts/alertsAcknowledge.js +6 -6
  17. package/src/handlers/alerts/alertsGet.js +11 -11
  18. package/src/handlers/analytics/activitySummaryGet.js +34 -35
  19. package/src/handlers/analytics/coachingGet.js +11 -11
  20. package/src/handlers/analytics/convergenceGet.js +236 -0
  21. package/src/handlers/analytics/developerScoreGet.js +41 -111
  22. package/src/handlers/collaborators/collaboratorInvite.js +1 -1
  23. package/src/handlers/company/companyUsersDelete.js +141 -0
  24. package/src/handlers/company/companyUsersGet.js +90 -0
  25. package/src/handlers/company/companyUsersPost.js +267 -0
  26. package/src/handlers/company/companyUsersPut.js +76 -0
  27. package/src/handlers/correlations/correlationsDeveloperGet.js +12 -12
  28. package/src/handlers/correlations/correlationsGet.js +8 -8
  29. package/src/handlers/correlations/correlationsProjectGet.js +5 -5
  30. package/src/handlers/enterprise/controlTowerGet.js +224 -0
  31. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +48 -9
  32. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +1 -3
  33. package/src/handlers/github/githubConnectionStatus.js +1 -1
  34. package/src/handlers/github/githubDiscoverPatterns.js +4 -2
  35. package/src/handlers/github/githubPatternsReview.js +7 -36
  36. package/src/handlers/health/healthGet.js +55 -0
  37. package/src/handlers/helpers/checkSuperAdmin.js +13 -14
  38. package/src/handlers/helpers/subscriptionTiers.js +27 -27
  39. package/src/handlers/mcp/mcpHandler.js +569 -0
  40. package/src/handlers/mcp/mindmeldMcpHandler.js +689 -0
  41. package/src/handlers/notifications/sendNotification.js +18 -18
  42. package/src/handlers/patterns/patternEvaluatePromotionPost.js +173 -0
  43. package/src/handlers/projects/projectCreate.js +124 -10
  44. package/src/handlers/projects/projectDelete.js +4 -4
  45. package/src/handlers/projects/projectGet.js +8 -8
  46. package/src/handlers/projects/projectUpdate.js +4 -4
  47. package/src/handlers/reports/aiLeverage.js +34 -30
  48. package/src/handlers/reports/engineeringInvestment.js +16 -16
  49. package/src/handlers/reports/riskForecast.js +41 -21
  50. package/src/handlers/reports/standardsRoi.js +101 -9
  51. package/src/handlers/scheduled/maturityUpdateJob.js +166 -0
  52. package/src/handlers/sessions/sessionStandardsPost.js +43 -7
  53. package/src/handlers/standards/discoveriesGet.js +93 -0
  54. package/src/handlers/standards/projectStandardsGet.js +2 -2
  55. package/src/handlers/standards/projectStandardsPut.js +2 -2
  56. package/src/handlers/standards/standardsRelevantPost.js +107 -12
  57. package/src/handlers/standards/standardsTransition.js +112 -15
  58. package/src/handlers/stripe/billingPortalPost.js +1 -1
  59. package/src/handlers/stripe/enterpriseCheckoutPost.js +2 -2
  60. package/src/handlers/stripe/subscriptionCreatePost.js +2 -2
  61. package/src/handlers/stripe/webhookPost.js +42 -14
  62. package/src/handlers/user/apiTokenCreate.js +71 -0
  63. package/src/handlers/user/apiTokenList.js +64 -0
  64. package/src/handlers/user/userSplashGet.js +90 -73
  65. package/src/handlers/users/cognitoPostConfirmation.js +37 -1
  66. package/src/handlers/users/cognitoPreSignUp.js +114 -0
  67. package/src/handlers/users/userGet.js +12 -8
  68. package/src/handlers/webhooks/githubWebhook.js +117 -125
  69. package/src/index.js +46 -51
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MindMeld - Claude Code SubagentStart Hook
4
+ *
5
+ * Injects cached standards context into subagents so they follow
6
+ * the same standards as the main session. Reads from the cache
7
+ * written by session-start.js — no API calls, < 50ms.
8
+ *
9
+ * @equilateral_ai/mindmeld v3.0.0
10
+ */
11
+
12
+ const path = require('path');
13
+ const fs = require('fs').promises;
14
+
15
+ async function injectSubagentContext() {
16
+ try {
17
+ // Read hook input from stdin
18
+ let input = {};
19
+ try {
20
+ const stdin = await readStdin();
21
+ if (stdin) {
22
+ input = JSON.parse(stdin);
23
+ }
24
+ } catch (e) {
25
+ // No stdin or invalid JSON — continue with defaults
26
+ }
27
+
28
+ const agentType = input.agent_type || 'unknown';
29
+ const cwd = input.cwd || process.cwd();
30
+
31
+ // Read cached subagent context from the project's .mindmeld dir
32
+ const cachePath = path.join(cwd, '.mindmeld', 'subagent-context.md');
33
+
34
+ let context;
35
+ try {
36
+ context = await fs.readFile(cachePath, 'utf-8');
37
+ } catch (error) {
38
+ if (error.code === 'ENOENT') {
39
+ // No cached context — session-start hasn't run or project not init'd
40
+ console.error('[MindMeld] No subagent context cache found');
41
+ return;
42
+ }
43
+ throw error;
44
+ }
45
+
46
+ // Check cache freshness — skip if older than 8 hours
47
+ const stat = await fs.stat(cachePath);
48
+ const ageHours = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);
49
+ if (ageHours > 8) {
50
+ console.error(`[MindMeld] Subagent context cache stale (${ageHours.toFixed(1)}h old), skipping`);
51
+ return;
52
+ }
53
+
54
+ // Check for active plan and append reference
55
+ const planRef = await getActivePlanRef(cwd);
56
+ if (planRef) {
57
+ context += '\n' + planRef;
58
+ }
59
+
60
+ // Output JSON with additionalContext for injection into the subagent
61
+ const output = {
62
+ hookSpecificOutput: {
63
+ hookEventName: 'SubagentStart',
64
+ additionalContext: context
65
+ }
66
+ };
67
+
68
+ console.log(JSON.stringify(output));
69
+ console.error(`[MindMeld] Injected context into ${agentType} subagent (${context.length} chars)`);
70
+
71
+ } catch (error) {
72
+ console.error('[MindMeld] SubagentStart hook error (non-fatal):', error.message);
73
+ // Don't block subagent creation
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Check for an active plan file and return a reference
79
+ * Looks for plan files modified in the last 2 hours
80
+ */
81
+ async function getActivePlanRef(cwd) {
82
+ const os = require('os');
83
+ const plansDir = path.join(os.homedir(), '.claude', 'plans');
84
+
85
+ try {
86
+ const files = await fs.readdir(plansDir);
87
+ const mdFiles = files.filter(f => f.endsWith('.md'));
88
+ const cutoff = Date.now() - (2 * 60 * 60 * 1000);
89
+
90
+ let mostRecent = null;
91
+ let mostRecentTime = 0;
92
+
93
+ for (const filename of mdFiles) {
94
+ const filePath = path.join(plansDir, filename);
95
+ const stat = await fs.stat(filePath);
96
+ if (stat.mtimeMs > cutoff && stat.mtimeMs > mostRecentTime) {
97
+ mostRecentTime = stat.mtimeMs;
98
+ mostRecent = { filename, filePath };
99
+ }
100
+ }
101
+
102
+ if (!mostRecent) return null;
103
+
104
+ // Read just the title
105
+ const content = await fs.readFile(mostRecent.filePath, 'utf-8');
106
+ const titleMatch = content.match(/^#\s+(?:Plan:\s*)?(.+)/m);
107
+ const title = titleMatch ? titleMatch[1].trim() : mostRecent.filename;
108
+
109
+ return `\n## Active Plan\nThere is an active plan: **${title}** (\`~/.claude/plans/${mostRecent.filename}\`). If your work relates to this plan, follow its guidance.`;
110
+
111
+ } catch (error) {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Read stdin with timeout
118
+ */
119
+ function readStdin() {
120
+ return new Promise((resolve) => {
121
+ let data = '';
122
+ const timeout = setTimeout(() => resolve(data), 500);
123
+
124
+ if (process.stdin.isTTY) {
125
+ clearTimeout(timeout);
126
+ resolve('');
127
+ return;
128
+ }
129
+
130
+ process.stdin.setEncoding('utf-8');
131
+ process.stdin.on('data', chunk => { data += chunk; });
132
+ process.stdin.on('end', () => {
133
+ clearTimeout(timeout);
134
+ resolve(data);
135
+ });
136
+ process.stdin.on('error', () => {
137
+ clearTimeout(timeout);
138
+ resolve('');
139
+ });
140
+ process.stdin.resume();
141
+ });
142
+ }
143
+
144
+ // Execute
145
+ injectSubagentContext()
146
+ .then(() => process.exit(0))
147
+ .catch(error => {
148
+ console.error('[MindMeld] Fatal error:', error);
149
+ process.exit(0); // Don't block subagent
150
+ });
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MindMeld - Claude Code SubagentStop Hook
4
+ *
5
+ * Lightweight pattern harvesting from subagent transcripts.
6
+ * Records what standards-relevant work subagents performed.
7
+ *
8
+ * @equilateral_ai/mindmeld v3.0.0
9
+ */
10
+
11
+ const path = require('path');
12
+ const fs = require('fs').promises;
13
+
14
+ async function onSubagentStop() {
15
+ try {
16
+ // Read hook input from stdin
17
+ let input = {};
18
+ try {
19
+ const stdin = await readStdin();
20
+ if (stdin) {
21
+ input = JSON.parse(stdin);
22
+ }
23
+ } catch (e) {
24
+ return; // No input, nothing to do
25
+ }
26
+
27
+ const agentType = input.agent_type || 'unknown';
28
+ const agentTranscriptPath = input.agent_transcript_path;
29
+ const cwd = input.cwd || process.cwd();
30
+
31
+ // Only harvest from code-writing agents, not explore/research
32
+ if (agentType === 'Explore') {
33
+ console.error('[MindMeld] Skipping harvest for Explore subagent');
34
+ return;
35
+ }
36
+
37
+ // Check if MindMeld is configured
38
+ try {
39
+ await fs.access(path.join(cwd, '.mindmeld', 'config.json'));
40
+ } catch (e) {
41
+ return; // Not a MindMeld project
42
+ }
43
+
44
+ // If we have a transcript path, scan for patterns
45
+ if (agentTranscriptPath) {
46
+ try {
47
+ const stat = await fs.stat(agentTranscriptPath);
48
+ // Only process transcripts under 500KB to stay fast
49
+ if (stat.size > 500 * 1024) {
50
+ console.error(`[MindMeld] Subagent transcript too large (${(stat.size / 1024).toFixed(0)}KB), skipping`);
51
+ return;
52
+ }
53
+
54
+ const transcript = await fs.readFile(agentTranscriptPath, 'utf-8');
55
+ const signals = extractSignals(transcript);
56
+
57
+ if (signals.length > 0) {
58
+ // Append signals to a local log for the pre-compact hook to pick up
59
+ const signalsPath = path.join(cwd, '.mindmeld', 'subagent-signals.jsonl');
60
+ const entries = signals.map(s => JSON.stringify({
61
+ ...s,
62
+ agent_type: agentType,
63
+ timestamp: new Date().toISOString()
64
+ }));
65
+ await fs.appendFile(signalsPath, entries.join('\n') + '\n');
66
+ console.error(`[MindMeld] Captured ${signals.length} signal(s) from ${agentType} subagent`);
67
+ }
68
+ } catch (error) {
69
+ console.error('[MindMeld] Transcript read failed:', error.message);
70
+ }
71
+ }
72
+
73
+ } catch (error) {
74
+ console.error('[MindMeld] SubagentStop hook error (non-fatal):', error.message);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Extract lightweight signals from subagent transcript
80
+ * Looks for file edits, patterns used, and standards-relevant keywords
81
+ */
82
+ function extractSignals(transcript) {
83
+ const signals = [];
84
+
85
+ // Detect file modifications (Write/Edit tool calls)
86
+ const fileEdits = transcript.match(/(?:Write|Edit).*?file_path['":\s]+([^\s'"}\]]+)/g);
87
+ if (fileEdits) {
88
+ const paths = [...new Set(fileEdits.map(m => {
89
+ const match = m.match(/file_path['":\s]+([^\s'"}\]]+)/);
90
+ return match ? match[1] : null;
91
+ }).filter(Boolean))];
92
+
93
+ if (paths.length > 0) {
94
+ signals.push({
95
+ type: 'files_modified',
96
+ files: paths.slice(0, 20)
97
+ });
98
+ }
99
+ }
100
+
101
+ // Detect pattern keywords
102
+ const patternKeywords = [
103
+ 'wrapHandler', 'executeQuery', 'createSuccessResponse', 'createErrorResponse',
104
+ 'rapport\\.', 'equilateral-standards', 'mindmeld'
105
+ ];
106
+
107
+ const detectedPatterns = [];
108
+ for (const keyword of patternKeywords) {
109
+ const regex = new RegExp(keyword, 'g');
110
+ const count = (transcript.match(regex) || []).length;
111
+ if (count > 0) {
112
+ detectedPatterns.push({ pattern: keyword, count });
113
+ }
114
+ }
115
+
116
+ if (detectedPatterns.length > 0) {
117
+ signals.push({
118
+ type: 'patterns_detected',
119
+ patterns: detectedPatterns
120
+ });
121
+ }
122
+
123
+ // Detect potential violations (anti-pattern keywords)
124
+ const antiPatterns = [
125
+ { pattern: 'mock\\s*data|mockData|fallback.*data', name: 'mock_data' },
126
+ { pattern: 'DefaultAuthorizer', name: 'default_authorizer' },
127
+ { pattern: 'getParameter|SSM.*runtime', name: 'ssm_runtime' },
128
+ { pattern: ':\\s*any\\b|:\\s*unknown\\b', name: 'weak_types' }
129
+ ];
130
+
131
+ const detectedAntiPatterns = [];
132
+ for (const ap of antiPatterns) {
133
+ const regex = new RegExp(ap.pattern, 'gi');
134
+ const count = (transcript.match(regex) || []).length;
135
+ if (count > 0) {
136
+ detectedAntiPatterns.push({ pattern: ap.name, count });
137
+ }
138
+ }
139
+
140
+ if (detectedAntiPatterns.length > 0) {
141
+ signals.push({
142
+ type: 'anti_patterns_detected',
143
+ anti_patterns: detectedAntiPatterns
144
+ });
145
+ }
146
+
147
+ return signals;
148
+ }
149
+
150
+ /**
151
+ * Read stdin with timeout
152
+ */
153
+ function readStdin() {
154
+ return new Promise((resolve) => {
155
+ let data = '';
156
+ const timeout = setTimeout(() => resolve(data), 500);
157
+
158
+ if (process.stdin.isTTY) {
159
+ clearTimeout(timeout);
160
+ resolve('');
161
+ return;
162
+ }
163
+
164
+ process.stdin.setEncoding('utf-8');
165
+ process.stdin.on('data', chunk => { data += chunk; });
166
+ process.stdin.on('end', () => {
167
+ clearTimeout(timeout);
168
+ resolve(data);
169
+ });
170
+ process.stdin.on('error', () => {
171
+ clearTimeout(timeout);
172
+ resolve('');
173
+ });
174
+ process.stdin.resume();
175
+ });
176
+ }
177
+
178
+ // Execute
179
+ onSubagentStop()
180
+ .then(() => process.exit(0))
181
+ .catch(error => {
182
+ console.error('[MindMeld] Fatal error:', error);
183
+ process.exit(0); // Don't block
184
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equilateral_ai/mindmeld",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Intelligent standards injection for AI coding sessions - context-aware, self-documenting, scales to large codebases",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "scripts/inject.js",
15
15
  "scripts/harvest.js",
16
16
  "scripts/repo-analyzer.js",
17
+ "scripts/mcp-bridge.js",
17
18
  "README.md"
18
19
  ],
19
20
  "publishConfig": {
@@ -40,10 +41,9 @@
40
41
  "test:session-start": "node scripts/test-claude-hooks.js --session-start",
41
42
  "test:pre-compact": "node scripts/test-claude-hooks.js --pre-compact",
42
43
  "test:benchmark": "node scripts/test-claude-hooks.js --benchmark",
43
- "test:relevance": "node scripts/test-relevance-detection.js",
44
- "test:curation": "node scripts/test-curation-engine.js",
45
- "test:validation": "node scripts/test-pattern-validation.js",
46
- "test:orchestrator": "node scripts/test-orchestrator.js"
44
+ "test:smoke": "jest --selectProjects smoke --testTimeout=15000",
45
+ "test:e2e": "cd e2e && npx playwright test",
46
+ "test:e2e:admin": "cd frontend/admin && npx playwright test"
47
47
  },
48
48
  "claudeCode": {
49
49
  "hooks": {
@@ -80,11 +80,12 @@
80
80
  "homepage": "https://mindmeld.dev",
81
81
  "dependencies": {
82
82
  "@aws-sdk/client-bedrock-runtime": "^3.460.0",
83
- "pg": "^8.11.3"
83
+ "@aws-sdk/client-ssm": "^3.985.0",
84
+ "pg": "^8.18.0"
84
85
  },
85
86
  "devDependencies": {
86
87
  "@aws-sdk/client-s3": "^3.460.0",
87
- "@aws-sdk/client-ses": "^3.968.0",
88
+ "@aws-sdk/client-ses": "^3.985.0",
88
89
  "jest": "^29.7.0"
89
90
  },
90
91
  "engines": {
@@ -126,7 +126,8 @@ function parseArgs(args) {
126
126
  since: '7d',
127
127
  commits: 10,
128
128
  dryRun: false,
129
- help: false
129
+ help: false,
130
+ token: null
130
131
  };
131
132
 
132
133
  for (let i = 0; i < args.length; i++) {
@@ -137,7 +138,8 @@ function parseArgs(args) {
137
138
  else if (arg === '--path' && args[i + 1]) { parsed.projectPath = args[++i]; }
138
139
  else if (arg === '--since' && args[i + 1]) { parsed.since = args[++i]; }
139
140
  else if (arg === '--commits' && args[i + 1]) { parsed.commits = parseInt(args[++i]); }
140
- else if (!parsed.command && ['init', 'inject', 'harvest', 'logout', 'login', 'status', 'standards', 'open'].includes(arg)) parsed.command = arg;
141
+ else if (arg === '--token' && args[i + 1]) { parsed.token = args[++i]; }
142
+ else if (!parsed.command && ['init', 'inject', 'harvest', 'logout', 'login', 'status', 'standards', 'open', 'mcp'].includes(arg)) parsed.command = arg;
141
143
  else if (!arg.startsWith('-') && !parsed.command) parsed.command = arg;
142
144
  else if (!arg.startsWith('-') && !parsed.projectPath) parsed.projectPath = arg;
143
145
  }
@@ -163,10 +165,12 @@ Commands:
163
165
  login Authenticate with MindMeld
164
166
  logout Clear stored authentication
165
167
  status Show current authentication status
168
+ mcp Start MCP stdio bridge for Cline, Cursor, Windsurf
166
169
 
167
170
  Options:
168
171
  --format <type> Output format: raw, cursorrules, windsurfrules, aider, claude
169
172
  --path <dir> Project path (default: current directory)
173
+ --token <tok> MindMeld API token for MCP bridge (or set MINDMELD_TOKEN)
170
174
  --help, -h Show this help message
171
175
 
172
176
  Examples:
@@ -176,6 +180,7 @@ Examples:
176
180
  mindmeld inject # Raw markdown to stdout
177
181
  mindmeld harvest # Capture patterns from git diff
178
182
  mindmeld status # Check auth status
183
+ MINDMELD_TOKEN=xxx mindmeld mcp # Start MCP bridge
179
184
 
180
185
  Works with: Claude Code, Cursor, Windsurf, Codex CLI, Aider, Ollama, LM Studio
181
186
 
@@ -488,18 +493,25 @@ async function bootstrapFromHistory(projectPath) {
488
493
  }
489
494
 
490
495
  async function configureClaudeHooks(projectPath) {
491
- const claudeDir = path.join(projectPath, '.claude');
496
+ // Register hooks globally (~/.claude/settings.json) so they run for ALL projects.
497
+ // The hook itself checks for .mindmeld/config.json and silently exits if not present.
498
+ const homeDir = require('os').homedir();
499
+ const claudeDir = path.join(homeDir, '.claude');
492
500
  const settingsPath = path.join(claudeDir, 'settings.json');
493
501
 
494
502
  const packageRoot = path.resolve(__dirname, '..');
495
503
  const sessionStartHook = path.join(packageRoot, 'hooks', 'session-start.js');
496
504
  const preCompactHook = path.join(packageRoot, 'hooks', 'pre-compact.js');
497
505
  const sessionEndHook = path.join(packageRoot, 'hooks', 'session-end.js');
506
+ const subagentStartHook = path.join(packageRoot, 'hooks', 'subagent-start.js');
507
+ const subagentStopHook = path.join(packageRoot, 'hooks', 'subagent-stop.js');
498
508
 
499
509
  try {
500
510
  await fs.access(sessionStartHook);
501
511
  await fs.access(preCompactHook);
502
512
  await fs.access(sessionEndHook);
513
+ await fs.access(subagentStartHook);
514
+ await fs.access(subagentStopHook);
503
515
  } catch (error) {
504
516
  // Expected: hooks not found in package
505
517
  if (error.code !== 'ENOENT') {
@@ -510,25 +522,43 @@ async function configureClaudeHooks(projectPath) {
510
522
  return;
511
523
  }
512
524
 
525
+ // Use the absolute path to the current node binary so hooks work
526
+ // even when Claude Code's subprocess doesn't have node on PATH
527
+ const nodeBin = process.execPath;
528
+
513
529
  const mindmeldHooks = {
514
530
  SessionStart: [{
515
531
  hooks: [{
516
532
  type: 'command',
517
- command: `node "${sessionStartHook}"`,
533
+ command: `${nodeBin} "${sessionStartHook}"`,
518
534
  timeout: 5
519
535
  }]
520
536
  }],
521
537
  PreCompact: [{
522
538
  hooks: [{
523
539
  type: 'command',
524
- command: `node "${preCompactHook}"`,
540
+ command: `${nodeBin} "${preCompactHook}"`,
525
541
  timeout: 30
526
542
  }]
527
543
  }],
544
+ SubagentStart: [{
545
+ hooks: [{
546
+ type: 'command',
547
+ command: `${nodeBin} "${subagentStartHook}"`,
548
+ timeout: 2
549
+ }]
550
+ }],
551
+ SubagentStop: [{
552
+ hooks: [{
553
+ type: 'command',
554
+ command: `${nodeBin} "${subagentStopHook}"`,
555
+ timeout: 5
556
+ }]
557
+ }],
528
558
  SessionEnd: [{
529
559
  hooks: [{
530
560
  type: 'command',
531
- command: `node "${sessionEndHook}"`,
561
+ command: `${nodeBin} "${sessionEndHook}"`,
532
562
  timeout: 5
533
563
  }]
534
564
  }]
@@ -552,45 +582,42 @@ async function configureClaudeHooks(projectPath) {
552
582
  settings.hooks = {};
553
583
  }
554
584
 
555
- const hasSessionStart = (settings.hooks.SessionStart || []).some(h =>
556
- h.hooks?.some(hk => hk.command?.includes('mindmeld') || hk.command?.includes('session-start'))
557
- );
558
- const hasPreCompact = (settings.hooks.PreCompact || []).some(h =>
559
- h.hooks?.some(hk => hk.command?.includes('mindmeld') || hk.command?.includes('pre-compact'))
560
- );
561
- const hasSessionEnd = (settings.hooks.SessionEnd || []).some(h =>
562
- h.hooks?.some(hk => hk.command?.includes('mindmeld') || hk.command?.includes('session-end'))
585
+ const hookCheck = (event, keyword) => (settings.hooks[event] || []).some(h =>
586
+ h.hooks?.some(hk => hk.command?.includes('mindmeld') || hk.command?.includes(keyword))
563
587
  );
564
588
 
565
- if (hasSessionStart && hasPreCompact && hasSessionEnd) {
589
+ const hasSessionStart = hookCheck('SessionStart', 'session-start');
590
+ const hasPreCompact = hookCheck('PreCompact', 'pre-compact');
591
+ const hasSessionEnd = hookCheck('SessionEnd', 'session-end');
592
+ const hasSubagentStart = hookCheck('SubagentStart', 'subagent-start');
593
+ const hasSubagentStop = hookCheck('SubagentStop', 'subagent-stop');
594
+
595
+ const allConfigured = hasSessionStart && hasPreCompact && hasSessionEnd
596
+ && hasSubagentStart && hasSubagentStop;
597
+
598
+ if (allConfigured) {
566
599
  console.log('ℹ️ Claude Code hooks already configured');
567
600
  return;
568
601
  }
569
602
 
570
- if (!hasSessionStart) {
571
- settings.hooks.SessionStart = [
572
- ...(settings.hooks.SessionStart || []),
573
- ...mindmeldHooks.SessionStart
574
- ];
575
- }
576
- if (!hasPreCompact) {
577
- settings.hooks.PreCompact = [
578
- ...(settings.hooks.PreCompact || []),
579
- ...mindmeldHooks.PreCompact
580
- ];
581
- }
582
- if (!hasSessionEnd) {
583
- settings.hooks.SessionEnd = [
584
- ...(settings.hooks.SessionEnd || []),
585
- ...mindmeldHooks.SessionEnd
586
- ];
603
+ // Add missing hooks
604
+ for (const [event, hooks] of Object.entries(mindmeldHooks)) {
605
+ const has = hookCheck(event, event.replace(/([A-Z])/g, '-$1').toLowerCase().slice(1));
606
+ if (!has) {
607
+ settings.hooks[event] = [
608
+ ...(settings.hooks[event] || []),
609
+ ...hooks
610
+ ];
611
+ }
587
612
  }
588
613
 
589
614
  await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
590
615
 
591
- console.log('✅ Claude Code hooks configured');
616
+ console.log('✅ Claude Code hooks configured (global ~/.claude/settings.json)');
592
617
  console.log(` SessionStart: ${sessionStartHook}`);
593
618
  console.log(` PreCompact: ${preCompactHook}`);
619
+ console.log(` SubagentStart: ${subagentStartHook}`);
620
+ console.log(` SubagentStop: ${subagentStopHook}`);
594
621
  console.log(` SessionEnd: ${sessionEndHook}`);
595
622
  }
596
623
 
@@ -723,6 +750,20 @@ if (args.command === 'init') {
723
750
  console.error('\n❌ Error:', error.message);
724
751
  process.exit(1);
725
752
  });
753
+ } else if (args.command === 'mcp') {
754
+ const { startBridge, resolveToken, showMcpHelp } = require('./mcp-bridge');
755
+ if (args.help) { showMcpHelp(); process.exit(0); }
756
+ const token = resolveToken(args.token);
757
+ if (!token) {
758
+ console.error('\nMindMeld MCP bridge requires an API token.\n');
759
+ console.error('Set one of:');
760
+ console.error(' 1. MINDMELD_TOKEN environment variable');
761
+ console.error(' 2. --token CLI argument');
762
+ console.error(' 3. Save to ~/.mindmeld/api-token\n');
763
+ console.error('Create a token at: https://app.mindmeld.dev/api-tokens\n');
764
+ process.exit(1);
765
+ }
766
+ startBridge(token);
726
767
  } else {
727
768
  console.error(`Unknown command: ${args.command}`);
728
769
  console.error('Run "mindmeld --help" for usage.');