@equilateral_ai/mindmeld 3.5.3 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/hooks/session-start.js +312 -85
  2. package/package.json +20 -14
  3. package/scripts/init-project.js +9 -23
  4. package/src/client/dbShim.js +16 -0
  5. package/src/core/AuthManager.js +3 -2
  6. package/src/handlers/helpers/dbOperations.js +9 -46
  7. package/src/index.js +2 -217
  8. package/src/utils/piiMask.js +16 -0
  9. package/scripts/harvest.js +0 -601
  10. package/scripts/inject.js +0 -409
  11. package/scripts/mcp-bridge.js +0 -220
  12. package/scripts/repo-analyzer.js +0 -870
  13. package/scripts/standards.js +0 -285
  14. package/src/collaboration/CollaborationPrompt.js +0 -460
  15. package/src/core/AlertEngine.js +0 -813
  16. package/src/core/AlertNotifier.js +0 -363
  17. package/src/core/CorrelationAnalyzer.js +0 -931
  18. package/src/core/CrossReferenceEngine.js +0 -624
  19. package/src/core/CurationEngine.js +0 -688
  20. package/src/core/DeprecationScheduler.js +0 -183
  21. package/src/core/LoadBearingDetector.js +0 -242
  22. package/src/core/NotificationService.js +0 -1032
  23. package/src/core/RapportOrchestrator.js +0 -632
  24. package/src/core/RelevanceDetector.js +0 -694
  25. package/src/core/StandardLifecycle.js +0 -244
  26. package/src/core/StandardsIngestion.js +0 -991
  27. package/src/core/TeamLoadBearingDetector.js +0 -431
  28. package/src/core/parsers/adrParser.js +0 -479
  29. package/src/core/parsers/cursorRulesParser.js +0 -564
  30. package/src/core/parsers/eslintParser.js +0 -439
  31. package/src/database/dbOperations.js +0 -105
  32. package/src/handlers/activity/activityGetMe.js +0 -98
  33. package/src/handlers/activity/activityGetTeam.js +0 -175
  34. package/src/handlers/admin/adminSetup.js +0 -216
  35. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  36. package/src/handlers/alerts/alertsGet.js +0 -250
  37. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  38. package/src/handlers/analytics/coachingGet.js +0 -361
  39. package/src/handlers/analytics/convergenceGet.js +0 -236
  40. package/src/handlers/analytics/developerScoreGet.js +0 -137
  41. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  42. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  43. package/src/handlers/collaborators/collaboratorList.js +0 -82
  44. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  45. package/src/handlers/collaborators/inviteAccept.js +0 -122
  46. package/src/handlers/company/companyUsersDelete.js +0 -141
  47. package/src/handlers/company/companyUsersGet.js +0 -90
  48. package/src/handlers/company/companyUsersPost.js +0 -267
  49. package/src/handlers/company/companyUsersPut.js +0 -76
  50. package/src/handlers/context/contextGet.js +0 -57
  51. package/src/handlers/context/invariantsGet.js +0 -74
  52. package/src/handlers/context/loopsGet.js +0 -82
  53. package/src/handlers/context/notesCreate.js +0 -74
  54. package/src/handlers/context/purposeGet.js +0 -78
  55. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  56. package/src/handlers/correlations/correlationsGet.js +0 -93
  57. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  58. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  59. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  60. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  61. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  62. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  63. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  64. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  65. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  66. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  67. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  68. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  69. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  70. package/src/handlers/github/githubConnectionStatus.js +0 -49
  71. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  72. package/src/handlers/github/githubOAuthCallback.js +0 -178
  73. package/src/handlers/github/githubOAuthStart.js +0 -59
  74. package/src/handlers/github/githubPatternsReview.js +0 -76
  75. package/src/handlers/github/githubReposList.js +0 -105
  76. package/src/handlers/health/healthGet.js +0 -55
  77. package/src/handlers/helpers/auditLogger.js +0 -201
  78. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  79. package/src/handlers/helpers/decisionFrames.js +0 -29
  80. package/src/handlers/helpers/errorHandler.js +0 -49
  81. package/src/handlers/helpers/index.js +0 -138
  82. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  83. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  84. package/src/handlers/helpers/predictiveCache.js +0 -51
  85. package/src/handlers/helpers/projectAccess.js +0 -88
  86. package/src/handlers/helpers/responseUtil.js +0 -55
  87. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  88. package/src/handlers/mcp/mcpHandler.js +0 -569
  89. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  90. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  91. package/src/handlers/notifications/getPreferences.js +0 -84
  92. package/src/handlers/notifications/sendNotification.js +0 -170
  93. package/src/handlers/notifications/updatePreferences.js +0 -316
  94. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  95. package/src/handlers/patterns/patternUsagePost.js +0 -182
  96. package/src/handlers/patterns/patternViolationPost.js +0 -185
  97. package/src/handlers/projects/projectCreate.js +0 -248
  98. package/src/handlers/projects/projectDelete.js +0 -82
  99. package/src/handlers/projects/projectGet.js +0 -95
  100. package/src/handlers/projects/projectUpdate.js +0 -117
  101. package/src/handlers/reports/aiLeverage.js +0 -210
  102. package/src/handlers/reports/engineeringInvestment.js +0 -132
  103. package/src/handlers/reports/riskForecast.js +0 -206
  104. package/src/handlers/reports/standardsRoi.js +0 -254
  105. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  106. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  107. package/src/handlers/scheduled/generateAlerts.js +0 -135
  108. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  109. package/src/handlers/scheduled/refreshActivity.js +0 -21
  110. package/src/handlers/scheduled/scanCompliance.js +0 -334
  111. package/src/handlers/sessions/sessionEndPost.js +0 -180
  112. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  113. package/src/handlers/standards/catalogGet.js +0 -185
  114. package/src/handlers/standards/catalogSync.js +0 -120
  115. package/src/handlers/standards/discoveriesGet.js +0 -89
  116. package/src/handlers/standards/projectStandardsGet.js +0 -129
  117. package/src/handlers/standards/projectStandardsPut.js +0 -151
  118. package/src/handlers/standards/standardsAuditGet.js +0 -65
  119. package/src/handlers/standards/standardsParseUpload.js +0 -149
  120. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  121. package/src/handlers/standards/standardsTransition.js +0 -161
  122. package/src/handlers/stripe/addonManagePost.js +0 -240
  123. package/src/handlers/stripe/billingPortalPost.js +0 -93
  124. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  125. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  126. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  127. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  128. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  129. package/src/handlers/stripe/webhookPost.js +0 -482
  130. package/src/handlers/user/apiTokenCreate.js +0 -71
  131. package/src/handlers/user/apiTokenList.js +0 -64
  132. package/src/handlers/user/userSplashAck.js +0 -91
  133. package/src/handlers/user/userSplashGet.js +0 -211
  134. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  135. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  136. package/src/handlers/users/userEntitlementsGet.js +0 -89
  137. package/src/handlers/users/userGet.js +0 -118
  138. package/src/handlers/users/userProfilePut.js +0 -77
  139. package/src/handlers/webhooks/githubWebhook.js +0 -215
@@ -1,1032 +0,0 @@
1
- /**
2
- * Rapport v3 - Notification Service
3
- *
4
- * Purpose: Sends notifications via email (SES) and Slack webhooks
5
- *
6
- * Notification Types:
7
- * - Pattern promotions (pattern validated/reinforced)
8
- * - Weekly digests (team activity summary)
9
- * - Critical violations (standards violations)
10
- * - Team alerts (attention alerts for managers)
11
- * - Curation candidates (patterns ready for review)
12
- *
13
- * Based on: Tim-Combo pattern with environment-based configuration
14
- */
15
-
16
- const { SESClient, SendEmailCommand, SendTemplatedEmailCommand } = require('@aws-sdk/client-ses');
17
-
18
- class NotificationService {
19
- constructor(config = {}) {
20
- this.config = {
21
- sesRegion: config.sesRegion || process.env.AWS_REGION || 'us-east-2',
22
- fromEmail: config.fromEmail || process.env.SES_FROM_EMAIL || 'noreply@mindmeld.dev',
23
- fromName: config.fromName || 'MindMeld',
24
- slackWebhookUrl: config.slackWebhookUrl || process.env.SLACK_WEBHOOK_URL,
25
- slackCriticalWebhookUrl: config.slackCriticalWebhookUrl || process.env.SLACK_CRITICAL_WEBHOOK_URL,
26
- appUrl: config.appUrl || process.env.APP_URL || 'https://mindmeld.dev',
27
- enabled: config.enabled !== false && process.env.NOTIFICATIONS_ENABLED !== 'false',
28
- ...config
29
- };
30
-
31
- // Initialize SES client (lazy - created once, reused across invocations)
32
- this.sesClient = null;
33
- }
34
-
35
- /**
36
- * Get SES client (cached single client pattern for Lambda)
37
- */
38
- getSESClient() {
39
- if (!this.sesClient) {
40
- this.sesClient = new SESClient({ region: this.config.sesRegion });
41
- }
42
- return this.sesClient;
43
- }
44
-
45
- /**
46
- * Send a notification based on type and preferences
47
- *
48
- * @param {Object} options - Notification options
49
- * @param {string} options.type - Notification type (pattern_promotion, weekly_digest, critical_violation, team_alert, curation_candidate)
50
- * @param {string} options.email - Recipient email
51
- * @param {Object} options.preferences - User notification preferences
52
- * @param {Object} options.data - Notification-specific data
53
- * @param {string} options.projectId - Optional project ID for project-specific preferences
54
- * @returns {Promise<Object>} Send result
55
- */
56
- async sendNotification({ type, email, preferences, data, projectId }) {
57
- if (!this.config.enabled) {
58
- console.log('[NotificationService] Notifications disabled, skipping');
59
- return { sent: false, reason: 'notifications_disabled' };
60
- }
61
-
62
- const results = { email: null, slack: null };
63
-
64
- // Check email preferences
65
- if (this.shouldSendEmail(type, preferences, projectId)) {
66
- try {
67
- results.email = await this.sendEmail(type, email, data);
68
- } catch (error) {
69
- console.error('[NotificationService] Email send failed:', error);
70
- results.email = { sent: false, error: error.message };
71
- }
72
- }
73
-
74
- // Check Slack preferences (for team/project notifications)
75
- if (this.shouldSendSlack(type, preferences, projectId, data)) {
76
- try {
77
- results.slack = await this.sendSlack(type, data);
78
- } catch (error) {
79
- console.error('[NotificationService] Slack send failed:', error);
80
- results.slack = { sent: false, error: error.message };
81
- }
82
- }
83
-
84
- return results;
85
- }
86
-
87
- /**
88
- * Check if email notification should be sent
89
- */
90
- shouldSendEmail(type, preferences, projectId) {
91
- if (!preferences) return true; // Default to enabled if no preferences
92
-
93
- // Check global email preference
94
- if (preferences.email_enabled === false) return false;
95
-
96
- // Check type-specific preference
97
- const typePrefs = preferences.notification_types || {};
98
- if (typePrefs[type]?.email === false) return false;
99
-
100
- // Check project-specific preference
101
- if (projectId && preferences.project_overrides) {
102
- const projectPrefs = preferences.project_overrides[projectId];
103
- if (projectPrefs?.email_enabled === false) return false;
104
- if (projectPrefs?.notification_types?.[type]?.email === false) return false;
105
- }
106
-
107
- return true;
108
- }
109
-
110
- /**
111
- * Check if Slack notification should be sent
112
- */
113
- shouldSendSlack(type, preferences, projectId, data) {
114
- // Must have webhook URL configured
115
- const isCritical = type === 'critical_violation' || data?.severity === 'critical';
116
- const webhookUrl = isCritical ? this.config.slackCriticalWebhookUrl : this.config.slackWebhookUrl;
117
-
118
- if (!webhookUrl) return false;
119
-
120
- if (!preferences) return true; // Default to enabled if no preferences
121
-
122
- // Check global Slack preference
123
- if (preferences.slack_enabled === false) return false;
124
-
125
- // Check type-specific preference
126
- const typePrefs = preferences.notification_types || {};
127
- if (typePrefs[type]?.slack === false) return false;
128
-
129
- // Check project-specific preference
130
- if (projectId && preferences.project_overrides) {
131
- const projectPrefs = preferences.project_overrides[projectId];
132
- if (projectPrefs?.slack_enabled === false) return false;
133
- if (projectPrefs?.notification_types?.[type]?.slack === false) return false;
134
- }
135
-
136
- return true;
137
- }
138
-
139
- /**
140
- * Send email notification via SES
141
- */
142
- async sendEmail(type, email, data) {
143
- const emailContent = this.buildEmailContent(type, data);
144
-
145
- const command = new SendEmailCommand({
146
- Source: `${this.config.fromName} <${this.config.fromEmail}>`,
147
- Destination: {
148
- ToAddresses: [email]
149
- },
150
- Message: {
151
- Subject: {
152
- Data: emailContent.subject,
153
- Charset: 'UTF-8'
154
- },
155
- Body: {
156
- Html: {
157
- Data: emailContent.html,
158
- Charset: 'UTF-8'
159
- },
160
- Text: {
161
- Data: emailContent.text,
162
- Charset: 'UTF-8'
163
- }
164
- }
165
- }
166
- });
167
-
168
- const result = await this.getSESClient().send(command);
169
-
170
- console.log(`[NotificationService] Email sent: ${type} to ${email}`);
171
- return { sent: true, messageId: result.MessageId };
172
- }
173
-
174
- /**
175
- * Build email content based on notification type
176
- */
177
- buildEmailContent(type, data) {
178
- switch (type) {
179
- case 'pattern_promotion':
180
- return this.buildPatternPromotionEmail(data);
181
- case 'weekly_digest':
182
- return this.buildWeeklyDigestEmail(data);
183
- case 'critical_violation':
184
- return this.buildCriticalViolationEmail(data);
185
- case 'team_alert':
186
- return this.buildTeamAlertEmail(data);
187
- case 'curation_candidate':
188
- return this.buildCurationCandidateEmail(data);
189
- default:
190
- return this.buildGenericEmail(type, data);
191
- }
192
- }
193
-
194
- /**
195
- * Build pattern promotion email
196
- */
197
- buildPatternPromotionEmail(data) {
198
- const { patternName, projectName, newMaturity, evidence } = data;
199
-
200
- const subject = `Pattern Promoted: ${patternName}`;
201
-
202
- const html = `
203
- <!DOCTYPE html>
204
- <html>
205
- <head>
206
- <style>
207
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
208
- .container { max-width: 600px; margin: 0 auto; padding: 20px; }
209
- .header { background: #2563eb; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
210
- .content { background: #f8fafc; padding: 20px; border-radius: 0 0 8px 8px; }
211
- .metric { display: inline-block; margin: 10px 20px 10px 0; }
212
- .metric-value { font-size: 24px; font-weight: bold; color: #2563eb; }
213
- .metric-label { font-size: 12px; color: #64748b; }
214
- .button { display: inline-block; background: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin-top: 20px; }
215
- .footer { margin-top: 20px; font-size: 12px; color: #94a3b8; }
216
- </style>
217
- </head>
218
- <body>
219
- <div class="container">
220
- <div class="header">
221
- <h1>Pattern Promoted</h1>
222
- </div>
223
- <div class="content">
224
- <h2>${patternName}</h2>
225
- <p>A pattern in <strong>${projectName}</strong> has been promoted to <strong>${newMaturity}</strong> status.</p>
226
-
227
- ${evidence ? `
228
- <div class="metrics">
229
- <div class="metric">
230
- <div class="metric-value">${(evidence.correlation * 100).toFixed(0)}%</div>
231
- <div class="metric-label">Success Rate</div>
232
- </div>
233
- <div class="metric">
234
- <div class="metric-value">${evidence.projectCount || 0}</div>
235
- <div class="metric-label">Projects</div>
236
- </div>
237
- <div class="metric">
238
- <div class="metric-value">${evidence.developerCount || 0}</div>
239
- <div class="metric-label">Developers</div>
240
- </div>
241
- <div class="metric">
242
- <div class="metric-value">${evidence.sessionCount || 0}</div>
243
- <div class="metric-label">Sessions</div>
244
- </div>
245
- </div>
246
- ` : ''}
247
-
248
- <a href="${this.config.appUrl}/dashboard/patterns" class="button">View Patterns</a>
249
- </div>
250
- <div class="footer">
251
- <p>You received this because you're a collaborator on ${projectName}.</p>
252
- <p><a href="${this.config.appUrl}/settings/notifications">Manage notification preferences</a></p>
253
- </div>
254
- </div>
255
- </body>
256
- </html>`;
257
-
258
- const text = `Pattern Promoted: ${patternName}
259
-
260
- A pattern in ${projectName} has been promoted to ${newMaturity} status.
261
-
262
- ${evidence ? `Evidence:
263
- - Success Rate: ${(evidence.correlation * 100).toFixed(0)}%
264
- - Projects: ${evidence.projectCount || 0}
265
- - Developers: ${evidence.developerCount || 0}
266
- - Sessions: ${evidence.sessionCount || 0}` : ''}
267
-
268
- View patterns: ${this.config.appUrl}/dashboard/patterns
269
-
270
- ---
271
- Manage notification preferences: ${this.config.appUrl}/settings/notifications`;
272
-
273
- return { subject, html, text };
274
- }
275
-
276
- /**
277
- * Build weekly digest email
278
- */
279
- buildWeeklyDigestEmail(data) {
280
- const {
281
- userName,
282
- weekStart,
283
- weekEnd,
284
- patternsLearned,
285
- patternsReinforced,
286
- sessionsCount,
287
- teamActivity,
288
- loadBearingElements
289
- } = data;
290
-
291
- const subject = `Weekly Digest: ${weekStart} - ${weekEnd}`;
292
-
293
- const html = `
294
- <!DOCTYPE html>
295
- <html>
296
- <head>
297
- <style>
298
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
299
- .container { max-width: 600px; margin: 0 auto; padding: 20px; }
300
- .header { background: linear-gradient(135deg, #2563eb, #7c3aed); color: white; padding: 20px; border-radius: 8px 8px 0 0; }
301
- .content { background: #f8fafc; padding: 20px; }
302
- .section { background: white; padding: 16px; border-radius: 8px; margin-bottom: 16px; }
303
- .section h3 { margin-top: 0; color: #1e293b; }
304
- .stat-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #e2e8f0; }
305
- .stat-row:last-child { border-bottom: none; }
306
- .stat-label { color: #64748b; }
307
- .stat-value { font-weight: 600; color: #1e293b; }
308
- .footer { margin-top: 20px; font-size: 12px; color: #94a3b8; text-align: center; }
309
- </style>
310
- </head>
311
- <body>
312
- <div class="container">
313
- <div class="header">
314
- <h1>Weekly Digest</h1>
315
- <p>${weekStart} - ${weekEnd}</p>
316
- </div>
317
- <div class="content">
318
- <p>Hi ${userName},</p>
319
- <p>Here's your team's collaboration summary for the week.</p>
320
-
321
- <div class="section">
322
- <h3>Your Activity</h3>
323
- <div class="stat-row">
324
- <span class="stat-label">Sessions</span>
325
- <span class="stat-value">${sessionsCount || 0}</span>
326
- </div>
327
- <div class="stat-row">
328
- <span class="stat-label">Patterns Learned</span>
329
- <span class="stat-value">${patternsLearned || 0}</span>
330
- </div>
331
- <div class="stat-row">
332
- <span class="stat-label">Patterns Reinforced</span>
333
- <span class="stat-value">${patternsReinforced || 0}</span>
334
- </div>
335
- </div>
336
-
337
- ${teamActivity ? `
338
- <div class="section">
339
- <h3>Team Activity</h3>
340
- <div class="stat-row">
341
- <span class="stat-label">Total Sessions</span>
342
- <span class="stat-value">${teamActivity.totalSessions || 0}</span>
343
- </div>
344
- <div class="stat-row">
345
- <span class="stat-label">Active Collaborators</span>
346
- <span class="stat-value">${teamActivity.activeCollaborators || 0}</span>
347
- </div>
348
- <div class="stat-row">
349
- <span class="stat-label">Shared Patterns</span>
350
- <span class="stat-value">${teamActivity.sharedPatterns || 0}</span>
351
- </div>
352
- </div>
353
- ` : ''}
354
-
355
- ${loadBearingElements && loadBearingElements.length > 0 ? `
356
- <div class="section">
357
- <h3>Load-Bearing Context (Top ${loadBearingElements.length})</h3>
358
- ${loadBearingElements.map(el => `
359
- <div class="stat-row">
360
- <span class="stat-label">${el.key}</span>
361
- <span class="stat-value">${(el.correlation * 100).toFixed(0)}% correlation</span>
362
- </div>
363
- `).join('')}
364
- </div>
365
- ` : ''}
366
- </div>
367
- <div class="footer">
368
- <p><a href="${this.config.appUrl}/dashboard">View Full Dashboard</a></p>
369
- <p><a href="${this.config.appUrl}/settings/notifications">Manage notification preferences</a></p>
370
- </div>
371
- </div>
372
- </body>
373
- </html>`;
374
-
375
- const text = `Weekly Digest: ${weekStart} - ${weekEnd}
376
-
377
- Hi ${userName},
378
-
379
- Here's your team's collaboration summary for the week.
380
-
381
- YOUR ACTIVITY
382
- - Sessions: ${sessionsCount || 0}
383
- - Patterns Learned: ${patternsLearned || 0}
384
- - Patterns Reinforced: ${patternsReinforced || 0}
385
-
386
- ${teamActivity ? `TEAM ACTIVITY
387
- - Total Sessions: ${teamActivity.totalSessions || 0}
388
- - Active Collaborators: ${teamActivity.activeCollaborators || 0}
389
- - Shared Patterns: ${teamActivity.sharedPatterns || 0}` : ''}
390
-
391
- View dashboard: ${this.config.appUrl}/dashboard
392
-
393
- ---
394
- Manage notification preferences: ${this.config.appUrl}/settings/notifications`;
395
-
396
- return { subject, html, text };
397
- }
398
-
399
- /**
400
- * Build critical violation email
401
- */
402
- buildCriticalViolationEmail(data) {
403
- const { violationType, projectName, standardName, details, filePath } = data;
404
-
405
- const subject = `[CRITICAL] Standards Violation: ${standardName}`;
406
-
407
- const html = `
408
- <!DOCTYPE html>
409
- <html>
410
- <head>
411
- <style>
412
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
413
- .container { max-width: 600px; margin: 0 auto; padding: 20px; }
414
- .header { background: #dc2626; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
415
- .content { background: #fef2f2; padding: 20px; border-radius: 0 0 8px 8px; }
416
- .alert-box { background: white; border-left: 4px solid #dc2626; padding: 16px; margin: 16px 0; }
417
- .code-block { background: #1e293b; color: #e2e8f0; padding: 16px; border-radius: 6px; font-family: monospace; overflow-x: auto; }
418
- .button { display: inline-block; background: #dc2626; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin-top: 20px; }
419
- .footer { margin-top: 20px; font-size: 12px; color: #94a3b8; }
420
- </style>
421
- </head>
422
- <body>
423
- <div class="container">
424
- <div class="header">
425
- <h1>Critical Standards Violation</h1>
426
- </div>
427
- <div class="content">
428
- <div class="alert-box">
429
- <strong>Standard:</strong> ${standardName}<br>
430
- <strong>Project:</strong> ${projectName}<br>
431
- ${filePath ? `<strong>File:</strong> ${filePath}<br>` : ''}
432
- <strong>Type:</strong> ${violationType}
433
- </div>
434
-
435
- <h3>Details</h3>
436
- <p>${details}</p>
437
-
438
- <a href="${this.config.appUrl}/dashboard/violations" class="button">View Violations</a>
439
- </div>
440
- <div class="footer">
441
- <p>This is a critical alert. You received this because you're a project admin.</p>
442
- <p><a href="${this.config.appUrl}/settings/notifications">Manage notification preferences</a></p>
443
- </div>
444
- </div>
445
- </body>
446
- </html>`;
447
-
448
- const text = `[CRITICAL] Standards Violation: ${standardName}
449
-
450
- Standard: ${standardName}
451
- Project: ${projectName}
452
- ${filePath ? `File: ${filePath}` : ''}
453
- Type: ${violationType}
454
-
455
- Details:
456
- ${details}
457
-
458
- View violations: ${this.config.appUrl}/dashboard/violations
459
-
460
- ---
461
- Manage notification preferences: ${this.config.appUrl}/settings/notifications`;
462
-
463
- return { subject, html, text };
464
- }
465
-
466
- /**
467
- * Build team alert email
468
- */
469
- buildTeamAlertEmail(data) {
470
- const { alertType, severity, userName, details, message } = data;
471
-
472
- const severityColors = {
473
- concern: '#dc2626',
474
- warning: '#f59e0b',
475
- info: '#3b82f6'
476
- };
477
-
478
- const subject = `[${severity.toUpperCase()}] Team Alert: ${message}`;
479
-
480
- const html = `
481
- <!DOCTYPE html>
482
- <html>
483
- <head>
484
- <style>
485
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
486
- .container { max-width: 600px; margin: 0 auto; padding: 20px; }
487
- .header { background: ${severityColors[severity] || '#3b82f6'}; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
488
- .content { background: #f8fafc; padding: 20px; border-radius: 0 0 8px 8px; }
489
- .alert-box { background: white; border-left: 4px solid ${severityColors[severity] || '#3b82f6'}; padding: 16px; margin: 16px 0; }
490
- .button { display: inline-block; background: ${severityColors[severity] || '#3b82f6'}; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin-top: 20px; }
491
- .footer { margin-top: 20px; font-size: 12px; color: #94a3b8; }
492
- </style>
493
- </head>
494
- <body>
495
- <div class="container">
496
- <div class="header">
497
- <h1>Team Alert</h1>
498
- </div>
499
- <div class="content">
500
- <div class="alert-box">
501
- <strong>Team Member:</strong> ${userName}<br>
502
- <strong>Alert Type:</strong> ${alertType}<br>
503
- <strong>Severity:</strong> ${severity}
504
- </div>
505
-
506
- <h3>${message}</h3>
507
-
508
- ${details ? `
509
- <ul>
510
- ${Object.entries(details).map(([key, value]) => `<li><strong>${key}:</strong> ${value}</li>`).join('')}
511
- </ul>
512
- ` : ''}
513
-
514
- <a href="${this.config.appUrl}/dashboard/alerts" class="button">View Alerts</a>
515
- </div>
516
- <div class="footer">
517
- <p>You received this because you're a team manager.</p>
518
- <p><a href="${this.config.appUrl}/settings/notifications">Manage notification preferences</a></p>
519
- </div>
520
- </div>
521
- </body>
522
- </html>`;
523
-
524
- const text = `[${severity.toUpperCase()}] Team Alert: ${message}
525
-
526
- Team Member: ${userName}
527
- Alert Type: ${alertType}
528
- Severity: ${severity}
529
-
530
- ${details ? Object.entries(details).map(([key, value]) => `${key}: ${value}`).join('\n') : ''}
531
-
532
- View alerts: ${this.config.appUrl}/dashboard/alerts
533
-
534
- ---
535
- Manage notification preferences: ${this.config.appUrl}/settings/notifications`;
536
-
537
- return { subject, html, text };
538
- }
539
-
540
- /**
541
- * Build curation candidate email
542
- */
543
- buildCurationCandidateEmail(data) {
544
- const { candidateId, patternName, category, evidence } = data;
545
-
546
- const subject = `New Curation Candidate: ${patternName}`;
547
-
548
- const html = `
549
- <!DOCTYPE html>
550
- <html>
551
- <head>
552
- <style>
553
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
554
- .container { max-width: 600px; margin: 0 auto; padding: 20px; }
555
- .header { background: linear-gradient(135deg, #059669, #10b981); color: white; padding: 20px; border-radius: 8px 8px 0 0; }
556
- .content { background: #f0fdf4; padding: 20px; border-radius: 0 0 8px 8px; }
557
- .metric { display: inline-block; margin: 10px 20px 10px 0; }
558
- .metric-value { font-size: 24px; font-weight: bold; color: #059669; }
559
- .metric-label { font-size: 12px; color: #64748b; }
560
- .button { display: inline-block; background: #059669; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin-top: 20px; margin-right: 10px; }
561
- .button-secondary { background: #64748b; }
562
- .footer { margin-top: 20px; font-size: 12px; color: #94a3b8; }
563
- </style>
564
- </head>
565
- <body>
566
- <div class="container">
567
- <div class="header">
568
- <h1>New Curation Candidate</h1>
569
- <p>Pattern ready for review</p>
570
- </div>
571
- <div class="content">
572
- <h2>${patternName}</h2>
573
- <p><strong>Category:</strong> ${category}</p>
574
-
575
- ${evidence ? `
576
- <div class="metrics">
577
- <div class="metric">
578
- <div class="metric-value">${(evidence.correlation * 100).toFixed(0)}%</div>
579
- <div class="metric-label">Success Rate</div>
580
- </div>
581
- <div class="metric">
582
- <div class="metric-value">${evidence.projectCount || 0}</div>
583
- <div class="metric-label">Projects</div>
584
- </div>
585
- <div class="metric">
586
- <div class="metric-value">${evidence.developerCount || 0}</div>
587
- <div class="metric-label">Developers</div>
588
- </div>
589
- <div class="metric">
590
- <div class="metric-value">${evidence.sessionCount || 0}</div>
591
- <div class="metric-label">Sessions</div>
592
- </div>
593
- </div>
594
- ` : ''}
595
-
596
- <p>This pattern has met all promotion criteria and is ready for human review before being promoted to .equilateral-standards/</p>
597
-
598
- <a href="${this.config.appUrl}/admin/curation/${candidateId}" class="button">Review Candidate</a>
599
- <a href="${this.config.appUrl}/admin/curation" class="button button-secondary">View All Candidates</a>
600
- </div>
601
- <div class="footer">
602
- <p>You received this because you're a standards curator.</p>
603
- <p><a href="${this.config.appUrl}/settings/notifications">Manage notification preferences</a></p>
604
- </div>
605
- </div>
606
- </body>
607
- </html>`;
608
-
609
- const text = `New Curation Candidate: ${patternName}
610
-
611
- Category: ${category}
612
-
613
- ${evidence ? `Evidence:
614
- - Success Rate: ${(evidence.correlation * 100).toFixed(0)}%
615
- - Projects: ${evidence.projectCount || 0}
616
- - Developers: ${evidence.developerCount || 0}
617
- - Sessions: ${evidence.sessionCount || 0}` : ''}
618
-
619
- This pattern has met all promotion criteria and is ready for human review.
620
-
621
- Review candidate: ${this.config.appUrl}/admin/curation/${candidateId}
622
- View all candidates: ${this.config.appUrl}/admin/curation
623
-
624
- ---
625
- Manage notification preferences: ${this.config.appUrl}/settings/notifications`;
626
-
627
- return { subject, html, text };
628
- }
629
-
630
- /**
631
- * Build generic email
632
- */
633
- buildGenericEmail(type, data) {
634
- const subject = `MindMeld Notification: ${type}`;
635
-
636
- const html = `
637
- <!DOCTYPE html>
638
- <html>
639
- <head>
640
- <style>
641
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
642
- .container { max-width: 600px; margin: 0 auto; padding: 20px; }
643
- .header { background: #2563eb; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
644
- .content { background: #f8fafc; padding: 20px; border-radius: 0 0 8px 8px; }
645
- .footer { margin-top: 20px; font-size: 12px; color: #94a3b8; }
646
- </style>
647
- </head>
648
- <body>
649
- <div class="container">
650
- <div class="header">
651
- <h1>MindMeld Notification</h1>
652
- </div>
653
- <div class="content">
654
- <p><strong>Type:</strong> ${type}</p>
655
- <pre>${JSON.stringify(data, null, 2)}</pre>
656
- </div>
657
- <div class="footer">
658
- <p><a href="${this.config.appUrl}/settings/notifications">Manage notification preferences</a></p>
659
- </div>
660
- </div>
661
- </body>
662
- </html>`;
663
-
664
- const text = `MindMeld Notification
665
-
666
- Type: ${type}
667
-
668
- ${JSON.stringify(data, null, 2)}
669
-
670
- ---
671
- Manage notification preferences: ${this.config.appUrl}/settings/notifications`;
672
-
673
- return { subject, html, text };
674
- }
675
-
676
- /**
677
- * Send Slack notification via webhook
678
- */
679
- async sendSlack(type, data) {
680
- const isCritical = type === 'critical_violation' || data?.severity === 'critical';
681
- const webhookUrl = isCritical ? this.config.slackCriticalWebhookUrl : this.config.slackWebhookUrl;
682
-
683
- if (!webhookUrl) {
684
- return { sent: false, reason: 'no_webhook_url' };
685
- }
686
-
687
- const payload = this.buildSlackPayload(type, data);
688
-
689
- const response = await fetch(webhookUrl, {
690
- method: 'POST',
691
- headers: { 'Content-Type': 'application/json' },
692
- body: JSON.stringify(payload)
693
- });
694
-
695
- if (!response.ok) {
696
- throw new Error(`Slack webhook failed: ${response.status}`);
697
- }
698
-
699
- console.log(`[NotificationService] Slack notification sent: ${type}`);
700
- return { sent: true };
701
- }
702
-
703
- /**
704
- * Build Slack message payload based on notification type
705
- */
706
- buildSlackPayload(type, data) {
707
- switch (type) {
708
- case 'pattern_promotion':
709
- return this.buildSlackPatternPromotion(data);
710
- case 'critical_violation':
711
- return this.buildSlackCriticalViolation(data);
712
- case 'team_alert':
713
- return this.buildSlackTeamAlert(data);
714
- case 'curation_candidate':
715
- return this.buildSlackCurationCandidate(data);
716
- default:
717
- return this.buildSlackGeneric(type, data);
718
- }
719
- }
720
-
721
- /**
722
- * Build Slack pattern promotion payload
723
- */
724
- buildSlackPatternPromotion(data) {
725
- const { patternName, projectName, newMaturity, evidence } = data;
726
-
727
- return {
728
- blocks: [
729
- {
730
- type: 'header',
731
- text: {
732
- type: 'plain_text',
733
- text: 'Pattern Promoted',
734
- emoji: true
735
- }
736
- },
737
- {
738
- type: 'section',
739
- fields: [
740
- {
741
- type: 'mrkdwn',
742
- text: `*Pattern:*\n${patternName}`
743
- },
744
- {
745
- type: 'mrkdwn',
746
- text: `*Project:*\n${projectName}`
747
- },
748
- {
749
- type: 'mrkdwn',
750
- text: `*New Status:*\n${newMaturity}`
751
- },
752
- {
753
- type: 'mrkdwn',
754
- text: `*Success Rate:*\n${evidence ? (evidence.correlation * 100).toFixed(0) + '%' : 'N/A'}`
755
- }
756
- ]
757
- },
758
- {
759
- type: 'actions',
760
- elements: [
761
- {
762
- type: 'button',
763
- text: {
764
- type: 'plain_text',
765
- text: 'View Patterns'
766
- },
767
- url: `${this.config.appUrl}/dashboard/patterns`
768
- }
769
- ]
770
- }
771
- ]
772
- };
773
- }
774
-
775
- /**
776
- * Build Slack critical violation payload
777
- */
778
- buildSlackCriticalViolation(data) {
779
- const { violationType, projectName, standardName, details, filePath } = data;
780
-
781
- return {
782
- blocks: [
783
- {
784
- type: 'header',
785
- text: {
786
- type: 'plain_text',
787
- text: 'CRITICAL: Standards Violation',
788
- emoji: true
789
- }
790
- },
791
- {
792
- type: 'section',
793
- text: {
794
- type: 'mrkdwn',
795
- text: `*${standardName}*\n${details}`
796
- }
797
- },
798
- {
799
- type: 'section',
800
- fields: [
801
- {
802
- type: 'mrkdwn',
803
- text: `*Project:*\n${projectName}`
804
- },
805
- {
806
- type: 'mrkdwn',
807
- text: `*Type:*\n${violationType}`
808
- }
809
- ]
810
- },
811
- filePath ? {
812
- type: 'context',
813
- elements: [
814
- {
815
- type: 'mrkdwn',
816
- text: `File: \`${filePath}\``
817
- }
818
- ]
819
- } : null,
820
- {
821
- type: 'actions',
822
- elements: [
823
- {
824
- type: 'button',
825
- text: {
826
- type: 'plain_text',
827
- text: 'View Violations'
828
- },
829
- style: 'danger',
830
- url: `${this.config.appUrl}/dashboard/violations`
831
- }
832
- ]
833
- }
834
- ].filter(Boolean)
835
- };
836
- }
837
-
838
- /**
839
- * Build Slack team alert payload
840
- */
841
- buildSlackTeamAlert(data) {
842
- const { alertType, severity, userName, message, details } = data;
843
-
844
- const severityEmoji = {
845
- concern: ':red_circle:',
846
- warning: ':large_orange_circle:',
847
- info: ':large_blue_circle:'
848
- };
849
-
850
- return {
851
- blocks: [
852
- {
853
- type: 'header',
854
- text: {
855
- type: 'plain_text',
856
- text: 'Team Alert',
857
- emoji: true
858
- }
859
- },
860
- {
861
- type: 'section',
862
- text: {
863
- type: 'mrkdwn',
864
- text: `${severityEmoji[severity] || ''} *${message}*`
865
- }
866
- },
867
- {
868
- type: 'section',
869
- fields: [
870
- {
871
- type: 'mrkdwn',
872
- text: `*Team Member:*\n${userName}`
873
- },
874
- {
875
- type: 'mrkdwn',
876
- text: `*Severity:*\n${severity}`
877
- }
878
- ]
879
- },
880
- details ? {
881
- type: 'context',
882
- elements: Object.entries(details).map(([key, value]) => ({
883
- type: 'mrkdwn',
884
- text: `*${key}:* ${value}`
885
- }))
886
- } : null,
887
- {
888
- type: 'actions',
889
- elements: [
890
- {
891
- type: 'button',
892
- text: {
893
- type: 'plain_text',
894
- text: 'View Alerts'
895
- },
896
- url: `${this.config.appUrl}/dashboard/alerts`
897
- }
898
- ]
899
- }
900
- ].filter(Boolean)
901
- };
902
- }
903
-
904
- /**
905
- * Build Slack curation candidate payload
906
- */
907
- buildSlackCurationCandidate(data) {
908
- const { candidateId, patternName, category, evidence } = data;
909
-
910
- return {
911
- blocks: [
912
- {
913
- type: 'header',
914
- text: {
915
- type: 'plain_text',
916
- text: 'New Curation Candidate',
917
- emoji: true
918
- }
919
- },
920
- {
921
- type: 'section',
922
- text: {
923
- type: 'mrkdwn',
924
- text: `*${patternName}*\nReady for review and promotion to .equilateral-standards/`
925
- }
926
- },
927
- {
928
- type: 'section',
929
- fields: [
930
- {
931
- type: 'mrkdwn',
932
- text: `*Category:*\n${category}`
933
- },
934
- {
935
- type: 'mrkdwn',
936
- text: `*Success Rate:*\n${evidence ? (evidence.correlation * 100).toFixed(0) + '%' : 'N/A'}`
937
- },
938
- {
939
- type: 'mrkdwn',
940
- text: `*Projects:*\n${evidence?.projectCount || 0}`
941
- },
942
- {
943
- type: 'mrkdwn',
944
- text: `*Developers:*\n${evidence?.developerCount || 0}`
945
- }
946
- ]
947
- },
948
- {
949
- type: 'actions',
950
- elements: [
951
- {
952
- type: 'button',
953
- text: {
954
- type: 'plain_text',
955
- text: 'Review Candidate'
956
- },
957
- style: 'primary',
958
- url: `${this.config.appUrl}/admin/curation/${candidateId}`
959
- }
960
- ]
961
- }
962
- ]
963
- };
964
- }
965
-
966
- /**
967
- * Build generic Slack payload
968
- */
969
- buildSlackGeneric(type, data) {
970
- return {
971
- blocks: [
972
- {
973
- type: 'header',
974
- text: {
975
- type: 'plain_text',
976
- text: `MindMeld: ${type}`,
977
- emoji: true
978
- }
979
- },
980
- {
981
- type: 'section',
982
- text: {
983
- type: 'mrkdwn',
984
- text: '```' + JSON.stringify(data, null, 2) + '```'
985
- }
986
- }
987
- ]
988
- };
989
- }
990
-
991
- /**
992
- * Send batch notifications (for weekly digests, etc.)
993
- *
994
- * @param {Array} recipients - Array of { email, preferences, data }
995
- * @param {string} type - Notification type
996
- * @returns {Promise<Object>} Batch results
997
- */
998
- async sendBatch(recipients, type) {
999
- const results = {
1000
- total: recipients.length,
1001
- sent: 0,
1002
- failed: 0,
1003
- skipped: 0,
1004
- errors: []
1005
- };
1006
-
1007
- for (const recipient of recipients) {
1008
- try {
1009
- const result = await this.sendNotification({
1010
- type,
1011
- email: recipient.email,
1012
- preferences: recipient.preferences,
1013
- data: recipient.data,
1014
- projectId: recipient.projectId
1015
- });
1016
-
1017
- if (result.email?.sent || result.slack?.sent) {
1018
- results.sent++;
1019
- } else {
1020
- results.skipped++;
1021
- }
1022
- } catch (error) {
1023
- results.failed++;
1024
- results.errors.push({ email: recipient.email, error: error.message });
1025
- }
1026
- }
1027
-
1028
- return results;
1029
- }
1030
- }
1031
-
1032
- module.exports = { NotificationService };