@equilateral_ai/mindmeld 3.0.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 +300 -0
- package/hooks/README.md +494 -0
- package/hooks/pre-compact.js +392 -0
- package/hooks/session-start.js +264 -0
- package/package.json +90 -0
- package/scripts/harvest.js +561 -0
- package/scripts/init-project.js +437 -0
- package/scripts/inject.js +388 -0
- package/src/collaboration/CollaborationPrompt.js +460 -0
- package/src/core/AlertEngine.js +813 -0
- package/src/core/AlertNotifier.js +363 -0
- package/src/core/CorrelationAnalyzer.js +774 -0
- package/src/core/CurationEngine.js +688 -0
- package/src/core/LLMPatternDetector.js +508 -0
- package/src/core/LoadBearingDetector.js +242 -0
- package/src/core/NotificationService.js +1032 -0
- package/src/core/PatternValidator.js +355 -0
- package/src/core/README.md +160 -0
- package/src/core/RapportOrchestrator.js +446 -0
- package/src/core/RelevanceDetector.js +577 -0
- package/src/core/StandardsIngestion.js +575 -0
- package/src/core/TeamLoadBearingDetector.js +431 -0
- package/src/database/dbOperations.js +105 -0
- package/src/handlers/activity/activityGetMe.js +98 -0
- package/src/handlers/activity/activityGetTeam.js +130 -0
- package/src/handlers/alerts/alertsAcknowledge.js +91 -0
- package/src/handlers/alerts/alertsGet.js +250 -0
- package/src/handlers/collaborators/collaboratorAdd.js +201 -0
- package/src/handlers/collaborators/collaboratorInvite.js +218 -0
- package/src/handlers/collaborators/collaboratorList.js +88 -0
- package/src/handlers/collaborators/collaboratorRemove.js +127 -0
- package/src/handlers/collaborators/inviteAccept.js +122 -0
- package/src/handlers/context/contextGet.js +57 -0
- package/src/handlers/context/invariantsGet.js +74 -0
- package/src/handlers/context/loopsGet.js +82 -0
- package/src/handlers/context/notesCreate.js +74 -0
- package/src/handlers/context/purposeGet.js +78 -0
- package/src/handlers/correlations/correlationsDeveloperGet.js +226 -0
- package/src/handlers/correlations/correlationsGet.js +93 -0
- package/src/handlers/correlations/correlationsProjectGet.js +161 -0
- package/src/handlers/github/githubConnectionStatus.js +49 -0
- package/src/handlers/github/githubDiscoverPatterns.js +364 -0
- package/src/handlers/github/githubOAuthCallback.js +166 -0
- package/src/handlers/github/githubOAuthStart.js +59 -0
- package/src/handlers/github/githubPatternsReview.js +109 -0
- package/src/handlers/github/githubReposList.js +105 -0
- package/src/handlers/helpers/checkSuperAdmin.js +85 -0
- package/src/handlers/helpers/dbOperations.js +53 -0
- package/src/handlers/helpers/errorHandler.js +49 -0
- package/src/handlers/helpers/index.js +106 -0
- package/src/handlers/helpers/lambdaWrapper.js +60 -0
- package/src/handlers/helpers/responseUtil.js +55 -0
- package/src/handlers/helpers/subscriptionTiers.js +1168 -0
- package/src/handlers/notifications/getPreferences.js +84 -0
- package/src/handlers/notifications/sendNotification.js +170 -0
- package/src/handlers/notifications/updatePreferences.js +316 -0
- package/src/handlers/patterns/patternUsagePost.js +182 -0
- package/src/handlers/patterns/patternViolationPost.js +185 -0
- package/src/handlers/projects/projectCreate.js +107 -0
- package/src/handlers/projects/projectDelete.js +82 -0
- package/src/handlers/projects/projectGet.js +95 -0
- package/src/handlers/projects/projectUpdate.js +118 -0
- package/src/handlers/reports/aiLeverage.js +206 -0
- package/src/handlers/reports/engineeringInvestment.js +132 -0
- package/src/handlers/reports/riskForecast.js +186 -0
- package/src/handlers/reports/standardsRoi.js +162 -0
- package/src/handlers/scheduled/analyzeCorrelations.js +178 -0
- package/src/handlers/scheduled/analyzeGitHistory.js +510 -0
- package/src/handlers/scheduled/generateAlerts.js +135 -0
- package/src/handlers/scheduled/refreshActivity.js +21 -0
- package/src/handlers/scheduled/scanCompliance.js +334 -0
- package/src/handlers/sessions/sessionEndPost.js +180 -0
- package/src/handlers/sessions/sessionStandardsPost.js +135 -0
- package/src/handlers/stripe/addonManagePost.js +240 -0
- package/src/handlers/stripe/billingPortalPost.js +93 -0
- package/src/handlers/stripe/enterpriseCheckoutPost.js +272 -0
- package/src/handlers/stripe/seatsUpdatePost.js +185 -0
- package/src/handlers/stripe/subscriptionCancelDelete.js +169 -0
- package/src/handlers/stripe/subscriptionCreatePost.js +221 -0
- package/src/handlers/stripe/subscriptionUpdatePut.js +163 -0
- package/src/handlers/stripe/webhookPost.js +454 -0
- package/src/handlers/users/cognitoPostConfirmation.js +150 -0
- package/src/handlers/users/userEntitlementsGet.js +89 -0
- package/src/handlers/users/userGet.js +114 -0
- package/src/handlers/webhooks/githubWebhook.js +223 -0
- package/src/index.js +969 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MindMeld - Claude Code Pre-Compact Hook
|
|
4
|
+
*
|
|
5
|
+
* Harvests patterns from session transcript and validates compliance.
|
|
6
|
+
* Runs before conversation compaction to learn from the session.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - LLM-powered pattern detection (when ANTHROPIC_API_KEY is set)
|
|
10
|
+
* - Regex fallback for fast pattern matching
|
|
11
|
+
* - Standards validation against .equilateral-standards
|
|
12
|
+
* - Pattern promotion tracking
|
|
13
|
+
*
|
|
14
|
+
* @equilateral_ai/mindmeld v3.0.0
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const fs = require('fs').promises;
|
|
19
|
+
|
|
20
|
+
// LLM Pattern Detection (optional - requires ANTHROPIC_API_KEY)
|
|
21
|
+
let LLMPatternDetector = null;
|
|
22
|
+
try {
|
|
23
|
+
const llmModule = require('../src/core/LLMPatternDetector');
|
|
24
|
+
LLMPatternDetector = llmModule.LLMPatternDetector;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
// LLM module not available - will use regex fallback
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Main hook execution
|
|
31
|
+
* @param {Object} sessionTranscript - Claude Code session data
|
|
32
|
+
*/
|
|
33
|
+
async function harvestPatterns(sessionTranscript) {
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// Fast bail if MindMeld not configured
|
|
38
|
+
const hasMindmeld = await checkMindmeldConfiguration();
|
|
39
|
+
if (!hasMindmeld) {
|
|
40
|
+
return { skipped: true, reason: 'No MindMeld configuration' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Load MindmeldClient with graceful degradation
|
|
44
|
+
const { MindmeldClient } = require('../src/index');
|
|
45
|
+
|
|
46
|
+
const mindmeld = new MindmeldClient({
|
|
47
|
+
projectPath: process.cwd()
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Extract session metadata
|
|
51
|
+
const sessionId = sessionTranscript.sessionId || generateSessionId();
|
|
52
|
+
const userId = sessionTranscript.userId || process.env.USER || 'unknown';
|
|
53
|
+
|
|
54
|
+
// 1. Detect patterns from session (LLM-powered or regex fallback)
|
|
55
|
+
let patterns = [];
|
|
56
|
+
let llmAnalysis = null;
|
|
57
|
+
// Bedrock uses AWS credentials, no API key needed
|
|
58
|
+
const useLLM = LLMPatternDetector && process.env.MINDMELD_USE_LLM !== 'false';
|
|
59
|
+
|
|
60
|
+
if (useLLM) {
|
|
61
|
+
// Use LLM for semantic pattern detection via AWS Bedrock
|
|
62
|
+
const detector = new LLMPatternDetector({
|
|
63
|
+
enabled: true,
|
|
64
|
+
model: process.env.MINDMELD_LLM_MODEL || 'us.anthropic.claude-3-5-haiku-20241022-v1:0',
|
|
65
|
+
region: process.env.AWS_REGION || 'us-east-2'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const transcriptText = typeof sessionTranscript === 'string'
|
|
69
|
+
? sessionTranscript
|
|
70
|
+
: sessionTranscript.transcript || JSON.stringify(sessionTranscript);
|
|
71
|
+
|
|
72
|
+
llmAnalysis = await detector.analyzeSessionTranscript(transcriptText, {
|
|
73
|
+
projectName: path.basename(process.cwd()),
|
|
74
|
+
filesChanged: sessionTranscript.filesChanged || []
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (llmAnalysis.success && llmAnalysis.patterns) {
|
|
78
|
+
patterns = llmAnalysis.patterns.map(p => ({
|
|
79
|
+
element: p.element,
|
|
80
|
+
type: p.type,
|
|
81
|
+
intent: p.intent,
|
|
82
|
+
category: p.category,
|
|
83
|
+
confidence: p.confidence,
|
|
84
|
+
evidence: p.evidence,
|
|
85
|
+
source: 'llm'
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
console.error(`[MindMeld] LLM detected ${patterns.length} patterns (model: ${llmAnalysis.model})`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fallback to regex-based detection if LLM unavailable or failed
|
|
93
|
+
if (patterns.length === 0) {
|
|
94
|
+
patterns = await mindmeld.detectPatternsFromSession(sessionTranscript);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!patterns || patterns.length === 0) {
|
|
98
|
+
return {
|
|
99
|
+
patternsDetected: 0,
|
|
100
|
+
violations: 0,
|
|
101
|
+
reinforced: 0,
|
|
102
|
+
llmUsed: useLLM && llmAnalysis?.success,
|
|
103
|
+
elapsed: Date.now() - startTime
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 2. Validate against standards
|
|
108
|
+
const validationResults = await validatePatterns(mindmeld, patterns);
|
|
109
|
+
|
|
110
|
+
// 3. Record violations
|
|
111
|
+
for (const result of validationResults.violations) {
|
|
112
|
+
await mindmeld.recordViolation({
|
|
113
|
+
pattern: result.pattern,
|
|
114
|
+
violations: result.violations,
|
|
115
|
+
sessionId: sessionId,
|
|
116
|
+
userId: userId
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 4. Reinforce valid patterns
|
|
121
|
+
for (const result of validationResults.valid) {
|
|
122
|
+
await mindmeld.reinforcePattern({
|
|
123
|
+
pattern: result.pattern,
|
|
124
|
+
sessionId: sessionId,
|
|
125
|
+
userId: userId
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 5. Check for promotion candidates
|
|
130
|
+
const candidates = await checkPromotionCandidates(mindmeld, validationResults.valid);
|
|
131
|
+
|
|
132
|
+
// 6. Check README staleness (documentation maintenance)
|
|
133
|
+
let readmeStatus = null;
|
|
134
|
+
try {
|
|
135
|
+
const { checkReadmeStaleness } = require('../scripts/check-readme-staleness');
|
|
136
|
+
readmeStatus = await checkReadmeStaleness(process.cwd());
|
|
137
|
+
|
|
138
|
+
if (readmeStatus.stale) {
|
|
139
|
+
console.error(`[MindMeld] 📄 README staleness detected:`);
|
|
140
|
+
console.error(`[MindMeld] Changes since last update: ${readmeStatus.changes}`);
|
|
141
|
+
console.error(`[MindMeld] Agent changes: ${readmeStatus.analysis.agentChanges}`);
|
|
142
|
+
console.error(`[MindMeld] Standards changes: ${readmeStatus.analysis.standardsChanges}`);
|
|
143
|
+
console.error(`[MindMeld] Significance: ${readmeStatus.analysis.significance}`);
|
|
144
|
+
|
|
145
|
+
if (readmeStatus.shouldTrigger) {
|
|
146
|
+
console.error(`[MindMeld] 🔧 Critical changes detected - README update recommended`);
|
|
147
|
+
console.error(`[MindMeld] Run: node -e "const L=require('./src/agents/specialists/LibrarianAgent');new L().generateProjectReadme({projectRoot:process.cwd()})"`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
// Non-fatal - don't break pattern harvesting
|
|
152
|
+
console.error(`[MindMeld] README check skipped:`, error.message);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 7. Log results
|
|
156
|
+
const elapsed = Date.now() - startTime;
|
|
157
|
+
const summary = {
|
|
158
|
+
patternsDetected: patterns.length,
|
|
159
|
+
violations: validationResults.violations.length,
|
|
160
|
+
reinforced: validationResults.valid.length,
|
|
161
|
+
promotionCandidates: candidates.length,
|
|
162
|
+
readmeStale: readmeStatus ? readmeStatus.stale : null,
|
|
163
|
+
readmeUpdateRecommended: readmeStatus ? readmeStatus.shouldTrigger : false,
|
|
164
|
+
llmUsed: useLLM && llmAnalysis?.success,
|
|
165
|
+
llmModel: llmAnalysis?.model || null,
|
|
166
|
+
llmSummary: llmAnalysis?.summary || null,
|
|
167
|
+
llmRecommendations: llmAnalysis?.recommendations || [],
|
|
168
|
+
elapsed: elapsed
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
console.error(`[MindMeld] Pattern harvesting complete in ${elapsed}ms`);
|
|
172
|
+
console.error(`[MindMeld] Detected: ${summary.patternsDetected}, Violations: ${summary.violations}, Reinforced: ${summary.reinforced}`);
|
|
173
|
+
|
|
174
|
+
if (candidates.length > 0) {
|
|
175
|
+
console.error(`[MindMeld] ${candidates.length} pattern(s) eligible for standards promotion:`);
|
|
176
|
+
for (const candidate of candidates) {
|
|
177
|
+
console.error(` - ${candidate.element} (correlation: ${(candidate.correlation * 100).toFixed(0)}%)`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return summary;
|
|
182
|
+
|
|
183
|
+
} catch (error) {
|
|
184
|
+
// Graceful degradation - never block compaction
|
|
185
|
+
console.error('[MindMeld] Hook error (non-fatal):', error.message);
|
|
186
|
+
return {
|
|
187
|
+
error: error.message,
|
|
188
|
+
patternsDetected: 0,
|
|
189
|
+
violations: 0,
|
|
190
|
+
reinforced: 0
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Check if MindMeld is configured for this project
|
|
197
|
+
*/
|
|
198
|
+
async function checkMindmeldConfiguration() {
|
|
199
|
+
try {
|
|
200
|
+
const mindmeldConfig = path.join(process.cwd(), '.mindmeld', 'config.json');
|
|
201
|
+
await fs.access(mindmeldConfig);
|
|
202
|
+
return true;
|
|
203
|
+
} catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Validate patterns against standards
|
|
210
|
+
*/
|
|
211
|
+
async function validatePatterns(mindmeld, patterns) {
|
|
212
|
+
const violations = [];
|
|
213
|
+
const valid = [];
|
|
214
|
+
|
|
215
|
+
for (const pattern of patterns) {
|
|
216
|
+
const validation = await mindmeld.validatePattern(pattern);
|
|
217
|
+
|
|
218
|
+
if (!validation.valid) {
|
|
219
|
+
violations.push({
|
|
220
|
+
pattern: pattern,
|
|
221
|
+
violations: validation.violations
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
valid.push({
|
|
225
|
+
pattern: pattern
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return { violations, valid };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Check for patterns eligible for promotion to standards
|
|
235
|
+
*/
|
|
236
|
+
async function checkPromotionCandidates(mindmeld, validPatterns) {
|
|
237
|
+
const candidates = [];
|
|
238
|
+
|
|
239
|
+
for (const result of validPatterns) {
|
|
240
|
+
const eligible = await mindmeld.evaluateForPromotion(result.pattern);
|
|
241
|
+
|
|
242
|
+
if (eligible) {
|
|
243
|
+
candidates.push(eligible);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return candidates;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Generate session ID
|
|
252
|
+
*/
|
|
253
|
+
function generateSessionId() {
|
|
254
|
+
return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Parse session transcript if provided as string
|
|
259
|
+
*/
|
|
260
|
+
function parseSessionTranscript(input) {
|
|
261
|
+
if (typeof input === 'string') {
|
|
262
|
+
try {
|
|
263
|
+
return JSON.parse(input);
|
|
264
|
+
} catch {
|
|
265
|
+
// Treat as raw transcript text
|
|
266
|
+
return {
|
|
267
|
+
transcript: input,
|
|
268
|
+
sessionId: generateSessionId()
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return input;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Generate context summary to survive compaction
|
|
277
|
+
* This output goes to stdout and becomes part of the compacted summary
|
|
278
|
+
*/
|
|
279
|
+
async function generatePostCompactContext(summary, llmAnalysis) {
|
|
280
|
+
const fsSync = require('fs');
|
|
281
|
+
const sections = [];
|
|
282
|
+
|
|
283
|
+
sections.push('# MindMeld Context Summary');
|
|
284
|
+
sections.push(`**Session harvested**: ${new Date().toISOString()}`);
|
|
285
|
+
sections.push(`**Project**: ${path.basename(process.cwd())}`);
|
|
286
|
+
sections.push('');
|
|
287
|
+
|
|
288
|
+
// Include session stats
|
|
289
|
+
sections.push('## Session Learning');
|
|
290
|
+
sections.push(`- Patterns detected: ${summary.patternsDetected || 0}`);
|
|
291
|
+
sections.push(`- Standards violations: ${summary.violations || 0}`);
|
|
292
|
+
sections.push(`- Patterns reinforced: ${summary.reinforced || 0}`);
|
|
293
|
+
if (summary.promotionCandidates > 0) {
|
|
294
|
+
sections.push(`- Promotion candidates: ${summary.promotionCandidates}`);
|
|
295
|
+
}
|
|
296
|
+
sections.push('');
|
|
297
|
+
|
|
298
|
+
// Include LLM recommendations if available
|
|
299
|
+
if (llmAnalysis?.recommendations && llmAnalysis.recommendations.length > 0) {
|
|
300
|
+
sections.push('## Session Recommendations');
|
|
301
|
+
for (const rec of llmAnalysis.recommendations) {
|
|
302
|
+
sections.push(`- ${rec}`);
|
|
303
|
+
}
|
|
304
|
+
sections.push('');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Include LLM summary if available
|
|
308
|
+
if (llmAnalysis?.summary) {
|
|
309
|
+
sections.push('## Session Summary');
|
|
310
|
+
sections.push(llmAnalysis.summary);
|
|
311
|
+
sections.push('');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Load relevant standards from .equilateral-standards if available
|
|
315
|
+
const standardsPath = path.join(process.cwd(), '.equilateral-standards');
|
|
316
|
+
try {
|
|
317
|
+
await fs.access(standardsPath);
|
|
318
|
+
|
|
319
|
+
// Load top-level standards index if available
|
|
320
|
+
const indexPath = path.join(standardsPath, 'index.json');
|
|
321
|
+
try {
|
|
322
|
+
const indexContent = await fs.readFile(indexPath, 'utf-8');
|
|
323
|
+
const index = JSON.parse(indexContent);
|
|
324
|
+
|
|
325
|
+
if (index.categories && index.categories.length > 0) {
|
|
326
|
+
sections.push('## Available Standards Categories');
|
|
327
|
+
for (const cat of index.categories.slice(0, 5)) {
|
|
328
|
+
sections.push(`- ${cat.name}: ${cat.description || ''}`);
|
|
329
|
+
}
|
|
330
|
+
sections.push('');
|
|
331
|
+
}
|
|
332
|
+
} catch {
|
|
333
|
+
// No index file - that's OK
|
|
334
|
+
}
|
|
335
|
+
} catch {
|
|
336
|
+
// No standards directory - that's OK
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Load project-specific patterns from .mindmeld if available
|
|
340
|
+
const mindmeldConfigPath = path.join(process.cwd(), '.mindmeld', 'config.json');
|
|
341
|
+
try {
|
|
342
|
+
const configContent = await fs.readFile(mindmeldConfigPath, 'utf-8');
|
|
343
|
+
const config = JSON.parse(configContent);
|
|
344
|
+
|
|
345
|
+
if (config.project_name) {
|
|
346
|
+
sections.push(`## Project: ${config.project_name}`);
|
|
347
|
+
}
|
|
348
|
+
if (config.team && config.team.length > 0) {
|
|
349
|
+
sections.push('**Team**: ' + config.team.map(t => t.name || t.email).join(', '));
|
|
350
|
+
}
|
|
351
|
+
sections.push('');
|
|
352
|
+
} catch {
|
|
353
|
+
// No mindmeld config - that's OK
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
sections.push('---');
|
|
357
|
+
sections.push('*Context preserved by MindMeld pre-compact hook - mindmeld.dev*');
|
|
358
|
+
|
|
359
|
+
return sections.join('\n');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Execute if called directly
|
|
363
|
+
if (require.main === module) {
|
|
364
|
+
// Read session transcript from stdin or args
|
|
365
|
+
const input = process.argv[2];
|
|
366
|
+
|
|
367
|
+
if (!input) {
|
|
368
|
+
console.error('[MindMeld] Usage: pre-compact.js <session-transcript-json>');
|
|
369
|
+
process.exit(0);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const sessionTranscript = parseSessionTranscript(input);
|
|
373
|
+
|
|
374
|
+
harvestPatterns(sessionTranscript)
|
|
375
|
+
.then(async (result) => {
|
|
376
|
+
// Generate and output context summary for post-compaction injection
|
|
377
|
+
const postCompactContext = await generatePostCompactContext(
|
|
378
|
+
result,
|
|
379
|
+
result.llmAnalysis || { summary: result.llmSummary, recommendations: result.llmRecommendations }
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
// Output to stdout - this survives compaction
|
|
383
|
+
console.log(postCompactContext);
|
|
384
|
+
process.exit(0);
|
|
385
|
+
})
|
|
386
|
+
.catch(error => {
|
|
387
|
+
console.error('[MindMeld] Fatal error:', error);
|
|
388
|
+
process.exit(0); // Don't block compaction
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
module.exports = { harvestPatterns, parseSessionTranscript, generatePostCompactContext };
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MindMeld - Claude Code Session Start Hook
|
|
4
|
+
*
|
|
5
|
+
* Injects ONLY relevant standards and team patterns at session start.
|
|
6
|
+
* Goal: < 500ms execution time for optimal UX.
|
|
7
|
+
*
|
|
8
|
+
* @equilateral_ai/mindmeld v3.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs').promises;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate a fingerprint for standards injection tracking
|
|
16
|
+
* @param {string} userId - User identifier
|
|
17
|
+
* @param {string} companyId - Company identifier
|
|
18
|
+
* @param {string} tier - Subscription tier
|
|
19
|
+
* @returns {string} Base64-encoded fingerprint wrapped in invisible comment
|
|
20
|
+
*/
|
|
21
|
+
function generateFingerprint(userId, companyId, tier) {
|
|
22
|
+
const fingerprint = {
|
|
23
|
+
user_id: userId || 'anonymous',
|
|
24
|
+
company_id: companyId || 'unknown',
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
subscription_tier: tier || 'free'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const base64Fingerprint = Buffer.from(JSON.stringify(fingerprint)).toString('base64');
|
|
30
|
+
return `<!-- fp:${base64Fingerprint} -->`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Load user and company info from MindMeld config
|
|
35
|
+
* @returns {Promise<{userId: string, companyId: string, tier: string}>}
|
|
36
|
+
*/
|
|
37
|
+
async function loadFingerprintConfig() {
|
|
38
|
+
try {
|
|
39
|
+
const configPath = path.join(process.cwd(), '.mindmeld', 'config.json');
|
|
40
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
41
|
+
const config = JSON.parse(configContent);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
userId: config.user_id || process.env.MINDMELD_USER_ID || 'anonymous',
|
|
45
|
+
companyId: config.company_id || process.env.MINDMELD_COMPANY_ID || 'unknown',
|
|
46
|
+
tier: config.subscription_tier || process.env.MINDMELD_TIER || 'free'
|
|
47
|
+
};
|
|
48
|
+
} catch {
|
|
49
|
+
// Fallback to environment variables
|
|
50
|
+
return {
|
|
51
|
+
userId: process.env.MINDMELD_USER_ID || 'anonymous',
|
|
52
|
+
companyId: process.env.MINDMELD_COMPANY_ID || 'unknown',
|
|
53
|
+
tier: process.env.MINDMELD_TIER || 'free'
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Main hook execution
|
|
60
|
+
*/
|
|
61
|
+
async function injectContext() {
|
|
62
|
+
const startTime = Date.now();
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Fast bail if MindMeld not configured
|
|
66
|
+
const hasMindmeld = await checkMindmeldConfiguration();
|
|
67
|
+
if (!hasMindmeld) {
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Load fingerprint config
|
|
72
|
+
const fingerprintConfig = await loadFingerprintConfig();
|
|
73
|
+
|
|
74
|
+
// Load MindmeldClient with graceful degradation
|
|
75
|
+
const { MindmeldClient } = require('../src/index');
|
|
76
|
+
|
|
77
|
+
const mindmeld = new MindmeldClient({
|
|
78
|
+
projectPath: process.cwd(),
|
|
79
|
+
standardsPath: path.join(process.cwd(), '.equilateral-standards')
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 1. Ensure standards are ingested (cached, fast check)
|
|
83
|
+
await mindmeld.ensureStandardsIngested();
|
|
84
|
+
|
|
85
|
+
// 2. Detect project context
|
|
86
|
+
const context = await mindmeld.detectProject();
|
|
87
|
+
|
|
88
|
+
if (!context) {
|
|
89
|
+
return ''; // No MindMeld project
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 3. Generate session ID for tracking
|
|
93
|
+
const sessionId = mindmeld.generateSessionId();
|
|
94
|
+
|
|
95
|
+
// 4. Identify relevant standards (NOT all 80+)
|
|
96
|
+
const relevance = await mindmeld.getRelevantStandards(context);
|
|
97
|
+
const relevantStandards = relevance.standards.slice(0, 10); // Top 10 most relevant
|
|
98
|
+
|
|
99
|
+
// 5. Record standards shown (Phase 7 - fire and forget, non-blocking)
|
|
100
|
+
mindmeld.recordStandardsShown(sessionId, relevantStandards);
|
|
101
|
+
|
|
102
|
+
// 6. Load team patterns
|
|
103
|
+
const teamPatterns = await mindmeld.loadProjectContext(context.projectId);
|
|
104
|
+
|
|
105
|
+
// 7. Get recent learning (last 7 days)
|
|
106
|
+
const recentLearning = await mindmeld.getRecentLearning(context.projectId, 7);
|
|
107
|
+
|
|
108
|
+
// 8. Build context injection with fingerprint
|
|
109
|
+
const injection = formatContextInjection({
|
|
110
|
+
project: context.projectName,
|
|
111
|
+
sessionId: sessionId,
|
|
112
|
+
collaborators: context.collaborators,
|
|
113
|
+
relevantStandards: relevantStandards,
|
|
114
|
+
teamPatterns: teamPatterns ? teamPatterns.filter(p => p.correlation > 0.7) : [],
|
|
115
|
+
recentLearning: recentLearning || [],
|
|
116
|
+
fingerprint: fingerprintConfig
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const elapsed = Date.now() - startTime;
|
|
120
|
+
console.error(`[MindMeld] Context injected in ${elapsed}ms`);
|
|
121
|
+
|
|
122
|
+
return injection;
|
|
123
|
+
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// Graceful degradation - never block Claude Code session
|
|
126
|
+
console.error('[MindMeld] Hook error (non-fatal):', error.message);
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Check if MindMeld is configured for this project
|
|
133
|
+
*/
|
|
134
|
+
async function checkMindmeldConfiguration() {
|
|
135
|
+
try {
|
|
136
|
+
const mindmeldConfig = path.join(process.cwd(), '.mindmeld', 'config.json');
|
|
137
|
+
await fs.access(mindmeldConfig);
|
|
138
|
+
return true;
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Format context injection for Claude Code
|
|
146
|
+
* @param {object} data - Context data to inject
|
|
147
|
+
* @param {object} data.fingerprint - Fingerprint config {userId, companyId, tier}
|
|
148
|
+
*/
|
|
149
|
+
function formatContextInjection(data) {
|
|
150
|
+
const {
|
|
151
|
+
project,
|
|
152
|
+
sessionId,
|
|
153
|
+
collaborators,
|
|
154
|
+
relevantStandards,
|
|
155
|
+
teamPatterns,
|
|
156
|
+
recentLearning,
|
|
157
|
+
fingerprint
|
|
158
|
+
} = data;
|
|
159
|
+
|
|
160
|
+
const sections = [];
|
|
161
|
+
|
|
162
|
+
// Generate fingerprint string
|
|
163
|
+
const fingerprintStr = fingerprint
|
|
164
|
+
? generateFingerprint(fingerprint.userId, fingerprint.companyId, fingerprint.tier)
|
|
165
|
+
: '';
|
|
166
|
+
|
|
167
|
+
// Header with session ID for tracking
|
|
168
|
+
sections.push(`# MindMeld Context - ${project}`);
|
|
169
|
+
if (sessionId) {
|
|
170
|
+
sections.push(`<!-- session:${sessionId} -->`);
|
|
171
|
+
}
|
|
172
|
+
sections.push('');
|
|
173
|
+
|
|
174
|
+
// Copyright/License notice
|
|
175
|
+
sections.push('© 2025 Equilateral AI (Pareidolia LLC). All rights reserved.');
|
|
176
|
+
sections.push('Licensed for use within MindMeld platform only. Redistribution prohibited.');
|
|
177
|
+
sections.push('');
|
|
178
|
+
|
|
179
|
+
// Collaborators
|
|
180
|
+
if (collaborators && collaborators.length > 0) {
|
|
181
|
+
sections.push('## Team');
|
|
182
|
+
sections.push(collaborators.map(c => `- ${c.name} (${c.email})`).join('\n'));
|
|
183
|
+
sections.push('');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Relevant standards (top 10 only)
|
|
187
|
+
if (relevantStandards && relevantStandards.length > 0) {
|
|
188
|
+
sections.push('## Relevant Standards');
|
|
189
|
+
sections.push('');
|
|
190
|
+
|
|
191
|
+
for (const standard of relevantStandards) {
|
|
192
|
+
sections.push(`### ${standard.element}`);
|
|
193
|
+
sections.push(`**Category**: ${standard.category}`);
|
|
194
|
+
// Add fingerprint to rule text
|
|
195
|
+
sections.push(`**Rule**: ${standard.rule} ${fingerprintStr}`);
|
|
196
|
+
|
|
197
|
+
if (standard.examples && standard.examples.length > 0) {
|
|
198
|
+
sections.push('');
|
|
199
|
+
sections.push('**Example**:');
|
|
200
|
+
sections.push('```javascript');
|
|
201
|
+
sections.push(standard.examples[0].code);
|
|
202
|
+
sections.push('```');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (standard.anti_patterns && standard.anti_patterns.length > 0) {
|
|
206
|
+
sections.push('');
|
|
207
|
+
sections.push('**Anti-patterns**:');
|
|
208
|
+
for (const antiPattern of standard.anti_patterns) {
|
|
209
|
+
sections.push(`- ❌ ${antiPattern.description}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
sections.push('');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Team patterns (high correlation only)
|
|
218
|
+
if (teamPatterns && teamPatterns.length > 0) {
|
|
219
|
+
sections.push('## Team Patterns');
|
|
220
|
+
sections.push('');
|
|
221
|
+
|
|
222
|
+
for (const pattern of teamPatterns) {
|
|
223
|
+
sections.push(`### ${pattern.element}`);
|
|
224
|
+
sections.push(`**Correlation**: ${(pattern.correlation * 100).toFixed(0)}%`);
|
|
225
|
+
sections.push(`**Usage**: ${pattern.usage_count} times across ${pattern.project_count} projects`);
|
|
226
|
+
// Add fingerprint to rule text
|
|
227
|
+
sections.push(`**Rule**: ${pattern.rule} ${fingerprintStr}`);
|
|
228
|
+
sections.push('');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Recent learning
|
|
233
|
+
if (recentLearning && recentLearning.length > 0) {
|
|
234
|
+
sections.push('## Recent Team Learning');
|
|
235
|
+
sections.push('');
|
|
236
|
+
|
|
237
|
+
for (const learning of recentLearning) {
|
|
238
|
+
sections.push(`- **${learning.pattern}**: ${learning.insight} (${learning.date})`);
|
|
239
|
+
}
|
|
240
|
+
sections.push('');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
sections.push('---');
|
|
244
|
+
sections.push('*Context provided by MindMeld - mindmeld.dev*');
|
|
245
|
+
|
|
246
|
+
return sections.join('\n');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Execute if called directly
|
|
250
|
+
if (require.main === module) {
|
|
251
|
+
injectContext()
|
|
252
|
+
.then(context => {
|
|
253
|
+
if (context) {
|
|
254
|
+
console.log(context);
|
|
255
|
+
}
|
|
256
|
+
process.exit(0);
|
|
257
|
+
})
|
|
258
|
+
.catch(error => {
|
|
259
|
+
console.error('[MindMeld] Fatal error:', error);
|
|
260
|
+
process.exit(0); // Don't block session start
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = { injectContext, formatContextInjection, generateFingerprint };
|