@equilateral_ai/mindmeld 3.3.1 → 3.5.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.
- package/README.md +1 -10
- package/hooks/pre-compact.js +213 -25
- package/hooks/session-end.js +112 -3
- package/hooks/session-start.js +635 -41
- package/hooks/subagent-start.js +150 -0
- package/hooks/subagent-stop.js +184 -0
- package/package.json +8 -7
- package/scripts/init-project.js +74 -33
- package/scripts/mcp-bridge.js +220 -0
- package/src/core/CorrelationAnalyzer.js +157 -0
- package/src/core/LLMPatternDetector.js +198 -0
- package/src/core/RelevanceDetector.js +123 -36
- package/src/core/StandardsIngestion.js +119 -18
- package/src/handlers/activity/activityGetMe.js +1 -1
- package/src/handlers/activity/activityGetTeam.js +100 -55
- package/src/handlers/admin/adminSetup.js +216 -0
- package/src/handlers/alerts/alertsAcknowledge.js +6 -6
- package/src/handlers/alerts/alertsGet.js +11 -11
- package/src/handlers/analytics/activitySummaryGet.js +34 -35
- package/src/handlers/analytics/coachingGet.js +11 -11
- package/src/handlers/analytics/convergenceGet.js +236 -0
- package/src/handlers/analytics/developerScoreGet.js +41 -111
- package/src/handlers/collaborators/collaboratorInvite.js +1 -1
- package/src/handlers/company/companyUsersDelete.js +141 -0
- package/src/handlers/company/companyUsersGet.js +90 -0
- package/src/handlers/company/companyUsersPost.js +267 -0
- package/src/handlers/company/companyUsersPut.js +76 -0
- package/src/handlers/correlations/correlationsDeveloperGet.js +12 -12
- package/src/handlers/correlations/correlationsGet.js +8 -8
- package/src/handlers/correlations/correlationsProjectGet.js +5 -5
- package/src/handlers/enterprise/controlTowerGet.js +224 -0
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +48 -9
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +1 -3
- package/src/handlers/github/githubConnectionStatus.js +1 -1
- package/src/handlers/github/githubDiscoverPatterns.js +4 -2
- package/src/handlers/github/githubPatternsReview.js +7 -36
- package/src/handlers/health/healthGet.js +55 -0
- package/src/handlers/helpers/checkSuperAdmin.js +13 -14
- package/src/handlers/helpers/mindmeldMcpCore.js +594 -0
- package/src/handlers/helpers/subscriptionTiers.js +27 -27
- package/src/handlers/mcp/mcpHandler.js +569 -0
- package/src/handlers/mcp/mindmeldMcpHandler.js +124 -0
- package/src/handlers/mcp/mindmeldMcpStreamHandler.js +243 -0
- package/src/handlers/notifications/sendNotification.js +18 -18
- package/src/handlers/patterns/patternEvaluatePromotionPost.js +173 -0
- package/src/handlers/projects/projectCreate.js +124 -10
- package/src/handlers/projects/projectDelete.js +4 -4
- package/src/handlers/projects/projectGet.js +8 -8
- package/src/handlers/projects/projectUpdate.js +4 -4
- package/src/handlers/reports/aiLeverage.js +34 -30
- package/src/handlers/reports/engineeringInvestment.js +16 -16
- package/src/handlers/reports/riskForecast.js +41 -21
- package/src/handlers/reports/standardsRoi.js +101 -9
- package/src/handlers/scheduled/maturityUpdateJob.js +166 -0
- package/src/handlers/sessions/sessionStandardsPost.js +43 -7
- package/src/handlers/standards/discoveriesGet.js +93 -0
- package/src/handlers/standards/projectStandardsGet.js +2 -2
- package/src/handlers/standards/projectStandardsPut.js +2 -2
- package/src/handlers/standards/standardsRelevantPost.js +107 -12
- package/src/handlers/standards/standardsTransition.js +112 -15
- package/src/handlers/stripe/billingPortalPost.js +1 -1
- package/src/handlers/stripe/enterpriseCheckoutPost.js +2 -2
- package/src/handlers/stripe/subscriptionCreatePost.js +2 -2
- package/src/handlers/stripe/webhookPost.js +42 -14
- package/src/handlers/user/apiTokenCreate.js +71 -0
- package/src/handlers/user/apiTokenList.js +64 -0
- package/src/handlers/user/userSplashGet.js +90 -73
- package/src/handlers/users/cognitoPostConfirmation.js +37 -1
- package/src/handlers/users/cognitoPreSignUp.js +114 -0
- package/src/handlers/users/userGet.js +15 -11
- package/src/handlers/webhooks/githubWebhook.js +117 -125
- package/src/index.js +8 -5
|
@@ -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
|
+
"version": "3.5.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:
|
|
44
|
-
"test:
|
|
45
|
-
"test:
|
|
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
|
-
"
|
|
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.
|
|
88
|
+
"@aws-sdk/client-ses": "^3.985.0",
|
|
88
89
|
"jest": "^29.7.0"
|
|
89
90
|
},
|
|
90
91
|
"engines": {
|
package/scripts/init-project.js
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
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:
|
|
533
|
+
command: `${nodeBin} "${sessionStartHook}"`,
|
|
518
534
|
timeout: 5
|
|
519
535
|
}]
|
|
520
536
|
}],
|
|
521
537
|
PreCompact: [{
|
|
522
538
|
hooks: [{
|
|
523
539
|
type: 'command',
|
|
524
|
-
command:
|
|
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:
|
|
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
|
|
556
|
-
h.hooks?.some(hk => hk.command?.includes('mindmeld') || hk.command?.includes(
|
|
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
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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.');
|