@equilateral_ai/mindmeld 3.5.3 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/hooks/session-start.js +312 -85
  2. package/package.json +21 -13
  3. package/scripts/init-project.js +9 -23
  4. package/scripts/repo-analyzer.js +118 -2
  5. package/src/client/dbShim.js +16 -0
  6. package/src/core/AuthManager.js +3 -2
  7. package/src/handlers/helpers/dbOperations.js +9 -46
  8. package/src/index.js +2 -217
  9. package/src/utils/piiMask.js +16 -0
  10. package/scripts/inject.js +0 -409
  11. package/scripts/mcp-bridge.js +0 -220
  12. package/scripts/standards.js +0 -285
  13. package/src/collaboration/CollaborationPrompt.js +0 -460
  14. package/src/core/AlertEngine.js +0 -813
  15. package/src/core/AlertNotifier.js +0 -363
  16. package/src/core/CorrelationAnalyzer.js +0 -931
  17. package/src/core/CrossReferenceEngine.js +0 -624
  18. package/src/core/CurationEngine.js +0 -688
  19. package/src/core/DeprecationScheduler.js +0 -183
  20. package/src/core/LoadBearingDetector.js +0 -242
  21. package/src/core/NotificationService.js +0 -1032
  22. package/src/core/RapportOrchestrator.js +0 -632
  23. package/src/core/RelevanceDetector.js +0 -694
  24. package/src/core/StandardLifecycle.js +0 -244
  25. package/src/core/StandardsIngestion.js +0 -991
  26. package/src/core/TeamLoadBearingDetector.js +0 -431
  27. package/src/core/parsers/adrParser.js +0 -479
  28. package/src/core/parsers/cursorRulesParser.js +0 -564
  29. package/src/core/parsers/eslintParser.js +0 -439
  30. package/src/database/dbOperations.js +0 -105
  31. package/src/handlers/activity/activityGetMe.js +0 -98
  32. package/src/handlers/activity/activityGetTeam.js +0 -175
  33. package/src/handlers/admin/adminSetup.js +0 -216
  34. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  35. package/src/handlers/alerts/alertsGet.js +0 -250
  36. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  37. package/src/handlers/analytics/coachingGet.js +0 -361
  38. package/src/handlers/analytics/convergenceGet.js +0 -236
  39. package/src/handlers/analytics/developerScoreGet.js +0 -137
  40. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  41. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  42. package/src/handlers/collaborators/collaboratorList.js +0 -82
  43. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  44. package/src/handlers/collaborators/inviteAccept.js +0 -122
  45. package/src/handlers/company/companyUsersDelete.js +0 -141
  46. package/src/handlers/company/companyUsersGet.js +0 -90
  47. package/src/handlers/company/companyUsersPost.js +0 -267
  48. package/src/handlers/company/companyUsersPut.js +0 -76
  49. package/src/handlers/context/contextGet.js +0 -57
  50. package/src/handlers/context/invariantsGet.js +0 -74
  51. package/src/handlers/context/loopsGet.js +0 -82
  52. package/src/handlers/context/notesCreate.js +0 -74
  53. package/src/handlers/context/purposeGet.js +0 -78
  54. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  55. package/src/handlers/correlations/correlationsGet.js +0 -93
  56. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  57. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  58. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  59. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  60. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  61. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  62. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  63. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  64. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  65. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  66. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  67. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  68. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  69. package/src/handlers/github/githubConnectionStatus.js +0 -49
  70. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  71. package/src/handlers/github/githubOAuthCallback.js +0 -178
  72. package/src/handlers/github/githubOAuthStart.js +0 -59
  73. package/src/handlers/github/githubPatternsReview.js +0 -76
  74. package/src/handlers/github/githubReposList.js +0 -105
  75. package/src/handlers/health/healthGet.js +0 -55
  76. package/src/handlers/helpers/auditLogger.js +0 -201
  77. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  78. package/src/handlers/helpers/decisionFrames.js +0 -29
  79. package/src/handlers/helpers/errorHandler.js +0 -49
  80. package/src/handlers/helpers/index.js +0 -138
  81. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  82. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  83. package/src/handlers/helpers/predictiveCache.js +0 -51
  84. package/src/handlers/helpers/projectAccess.js +0 -88
  85. package/src/handlers/helpers/responseUtil.js +0 -55
  86. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  87. package/src/handlers/mcp/mcpHandler.js +0 -569
  88. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  89. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  90. package/src/handlers/notifications/getPreferences.js +0 -84
  91. package/src/handlers/notifications/sendNotification.js +0 -170
  92. package/src/handlers/notifications/updatePreferences.js +0 -316
  93. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  94. package/src/handlers/patterns/patternUsagePost.js +0 -182
  95. package/src/handlers/patterns/patternViolationPost.js +0 -185
  96. package/src/handlers/projects/projectCreate.js +0 -248
  97. package/src/handlers/projects/projectDelete.js +0 -82
  98. package/src/handlers/projects/projectGet.js +0 -95
  99. package/src/handlers/projects/projectUpdate.js +0 -117
  100. package/src/handlers/reports/aiLeverage.js +0 -210
  101. package/src/handlers/reports/engineeringInvestment.js +0 -132
  102. package/src/handlers/reports/riskForecast.js +0 -206
  103. package/src/handlers/reports/standardsRoi.js +0 -254
  104. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  105. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  106. package/src/handlers/scheduled/generateAlerts.js +0 -135
  107. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  108. package/src/handlers/scheduled/refreshActivity.js +0 -21
  109. package/src/handlers/scheduled/scanCompliance.js +0 -334
  110. package/src/handlers/sessions/sessionEndPost.js +0 -180
  111. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  112. package/src/handlers/standards/catalogGet.js +0 -185
  113. package/src/handlers/standards/catalogSync.js +0 -120
  114. package/src/handlers/standards/discoveriesGet.js +0 -89
  115. package/src/handlers/standards/projectStandardsGet.js +0 -129
  116. package/src/handlers/standards/projectStandardsPut.js +0 -151
  117. package/src/handlers/standards/standardsAuditGet.js +0 -65
  118. package/src/handlers/standards/standardsParseUpload.js +0 -149
  119. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  120. package/src/handlers/standards/standardsTransition.js +0 -161
  121. package/src/handlers/stripe/addonManagePost.js +0 -240
  122. package/src/handlers/stripe/billingPortalPost.js +0 -93
  123. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  124. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  125. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  126. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  127. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  128. package/src/handlers/stripe/webhookPost.js +0 -482
  129. package/src/handlers/user/apiTokenCreate.js +0 -71
  130. package/src/handlers/user/apiTokenList.js +0 -64
  131. package/src/handlers/user/userSplashAck.js +0 -91
  132. package/src/handlers/user/userSplashGet.js +0 -211
  133. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  134. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  135. package/src/handlers/users/userEntitlementsGet.js +0 -89
  136. package/src/handlers/users/userGet.js +0 -118
  137. package/src/handlers/users/userProfilePut.js +0 -77
  138. 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 };