@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,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rapport v3 - Alert Notifier
|
|
3
|
+
*
|
|
4
|
+
* Purpose: Sends notifications when attention alerts are generated
|
|
5
|
+
*
|
|
6
|
+
* Integration: Called by generateAlerts scheduled handler
|
|
7
|
+
* Channels: Email and Slack based on user preferences
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Note: When used in Lambda, executeQuery comes from helpers (injected via setExecuteQuery)
|
|
11
|
+
// When used in CLI/scripts, it can be provided via constructor
|
|
12
|
+
let executeQueryFn = null;
|
|
13
|
+
|
|
14
|
+
function setExecuteQuery(queryFn) {
|
|
15
|
+
executeQueryFn = queryFn;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getExecuteQuery() {
|
|
19
|
+
if (!executeQueryFn) {
|
|
20
|
+
// Try to require from database/dbOperations as fallback
|
|
21
|
+
try {
|
|
22
|
+
return require('../handlers/helpers/dbOperations').executeQuery;
|
|
23
|
+
} catch (e) {
|
|
24
|
+
throw new Error('AlertNotifier: executeQuery not initialized. Call setExecuteQuery() first.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return executeQueryFn;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { NotificationService } = require('./NotificationService');
|
|
31
|
+
|
|
32
|
+
class AlertNotifier {
|
|
33
|
+
constructor(config = {}) {
|
|
34
|
+
this.config = {
|
|
35
|
+
notifyOnSeverity: config.notifyOnSeverity || ['concern', 'warning'], // Which severities trigger notifications
|
|
36
|
+
slackOnlyForCritical: config.slackOnlyForCritical !== false, // Only send Slack for critical/concern alerts
|
|
37
|
+
...config
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
this.notificationService = config.notificationService || new NotificationService();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Notify managers about new alerts
|
|
45
|
+
*
|
|
46
|
+
* @param {Array} alerts - List of alert objects
|
|
47
|
+
* @returns {Promise<Object>} Notification results
|
|
48
|
+
*/
|
|
49
|
+
async notifyForAlerts(alerts) {
|
|
50
|
+
if (!alerts || alerts.length === 0) {
|
|
51
|
+
return { notified: 0, skipped: 0, failed: 0 };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const results = {
|
|
55
|
+
notified: 0,
|
|
56
|
+
skipped: 0,
|
|
57
|
+
failed: 0,
|
|
58
|
+
errors: []
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Group alerts by company for batch processing
|
|
62
|
+
const alertsByCompany = this.groupAlertsByCompany(alerts);
|
|
63
|
+
|
|
64
|
+
for (const [companyId, companyAlerts] of Object.entries(alertsByCompany)) {
|
|
65
|
+
try {
|
|
66
|
+
// Get managers for this company
|
|
67
|
+
const managers = await this.getCompanyManagers(companyId);
|
|
68
|
+
|
|
69
|
+
if (managers.length === 0) {
|
|
70
|
+
console.log(`[AlertNotifier] No managers found for company ${companyId}`);
|
|
71
|
+
results.skipped += companyAlerts.length;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Send notifications to each manager
|
|
76
|
+
for (const manager of managers) {
|
|
77
|
+
const preferences = await this.getUserPreferences(manager.email_address);
|
|
78
|
+
|
|
79
|
+
for (const alert of companyAlerts) {
|
|
80
|
+
// Check if alert severity warrants notification
|
|
81
|
+
if (!this.config.notifyOnSeverity.includes(alert.severity)) {
|
|
82
|
+
results.skipped++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const sendResult = await this.notificationService.sendNotification({
|
|
88
|
+
type: 'team_alert',
|
|
89
|
+
email: manager.email_address,
|
|
90
|
+
preferences: preferences,
|
|
91
|
+
data: {
|
|
92
|
+
alertId: alert.alert_id,
|
|
93
|
+
alertType: alert.alert_type,
|
|
94
|
+
severity: alert.severity,
|
|
95
|
+
userName: alert.user_name || alert.email_address,
|
|
96
|
+
details: alert.details,
|
|
97
|
+
message: this.getAlertMessage(alert.alert_type, alert.details),
|
|
98
|
+
referenceType: 'attention_alert',
|
|
99
|
+
referenceId: String(alert.alert_id)
|
|
100
|
+
},
|
|
101
|
+
projectId: null // Alerts are company-wide
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (sendResult.email?.sent || sendResult.slack?.sent) {
|
|
105
|
+
results.notified++;
|
|
106
|
+
|
|
107
|
+
// Log the notification
|
|
108
|
+
await this.logNotification(
|
|
109
|
+
manager.email_address,
|
|
110
|
+
'team_alert',
|
|
111
|
+
sendResult.email?.sent ? 'email' : 'slack',
|
|
112
|
+
'sent',
|
|
113
|
+
alert
|
|
114
|
+
);
|
|
115
|
+
} else {
|
|
116
|
+
results.skipped++;
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
results.failed++;
|
|
120
|
+
results.errors.push({
|
|
121
|
+
alertId: alert.alert_id,
|
|
122
|
+
manager: manager.email_address,
|
|
123
|
+
error: error.message
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Log failed notification
|
|
127
|
+
await this.logNotification(
|
|
128
|
+
manager.email_address,
|
|
129
|
+
'team_alert',
|
|
130
|
+
'email',
|
|
131
|
+
'failed',
|
|
132
|
+
alert,
|
|
133
|
+
error.message
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(`[AlertNotifier] Error processing company ${companyId}:`, error);
|
|
140
|
+
results.failed += companyAlerts.length;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`[AlertNotifier] Results: ${results.notified} notified, ${results.skipped} skipped, ${results.failed} failed`);
|
|
145
|
+
return results;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Group alerts by company ID
|
|
150
|
+
*/
|
|
151
|
+
groupAlertsByCompany(alerts) {
|
|
152
|
+
const grouped = {};
|
|
153
|
+
for (const alert of alerts) {
|
|
154
|
+
const companyId = alert.company_id || 'unknown';
|
|
155
|
+
if (!grouped[companyId]) {
|
|
156
|
+
grouped[companyId] = [];
|
|
157
|
+
}
|
|
158
|
+
grouped[companyId].push(alert);
|
|
159
|
+
}
|
|
160
|
+
return grouped;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get managers for a company
|
|
165
|
+
*/
|
|
166
|
+
async getCompanyManagers(companyId) {
|
|
167
|
+
const executeQuery = getExecuteQuery();
|
|
168
|
+
const query = `
|
|
169
|
+
SELECT DISTINCT
|
|
170
|
+
ue."Email_Address" as email_address,
|
|
171
|
+
u."User_Display_Name" as user_name
|
|
172
|
+
FROM "UserEntitlements" ue
|
|
173
|
+
JOIN "Users" u ON ue."Email_Address" = u."Email_Address"
|
|
174
|
+
WHERE ue."Company_ID" = $1
|
|
175
|
+
AND (ue."Manager" = true OR ue."Admin" = true OR u."Super_Admin" = true)
|
|
176
|
+
AND u."active" = true
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
const result = await executeQuery(query, [companyId]);
|
|
180
|
+
return result.rows;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get user notification preferences
|
|
185
|
+
*/
|
|
186
|
+
async getUserPreferences(email) {
|
|
187
|
+
const executeQuery = getExecuteQuery();
|
|
188
|
+
const query = `SELECT rapport.get_notification_preferences($1) as preferences`;
|
|
189
|
+
const result = await executeQuery(query, [email]);
|
|
190
|
+
return result.rows[0]?.preferences || null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Generate human-readable alert message
|
|
195
|
+
*/
|
|
196
|
+
getAlertMessage(alertType, details) {
|
|
197
|
+
switch (alertType) {
|
|
198
|
+
case 'stale_commits':
|
|
199
|
+
return `No commits in ${details.days_since_commit} days`;
|
|
200
|
+
case 'low_conversion':
|
|
201
|
+
return `Low session-to-commit conversion: ${details.conversion_pct}%`;
|
|
202
|
+
case 'no_ai_usage':
|
|
203
|
+
return `Active committer not using AI assistance`;
|
|
204
|
+
case 'declining_activity':
|
|
205
|
+
return `Activity declining over past ${details.period || 'week'}`;
|
|
206
|
+
case 'blocked_developer':
|
|
207
|
+
return `Developer appears blocked - ${details.reason || 'multiple failed sessions'}`;
|
|
208
|
+
case 'pattern_violation':
|
|
209
|
+
return `Repeated pattern violations detected`;
|
|
210
|
+
default:
|
|
211
|
+
return `Alert: ${alertType}`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Log notification to database
|
|
217
|
+
*/
|
|
218
|
+
async logNotification(email, type, channel, status, alert, errorMessage = null) {
|
|
219
|
+
try {
|
|
220
|
+
const executeQuery = getExecuteQuery();
|
|
221
|
+
await executeQuery(`
|
|
222
|
+
SELECT rapport.log_notification($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
223
|
+
`, [
|
|
224
|
+
email,
|
|
225
|
+
type,
|
|
226
|
+
channel,
|
|
227
|
+
status,
|
|
228
|
+
null, // project_id
|
|
229
|
+
'attention_alert',
|
|
230
|
+
String(alert.alert_id),
|
|
231
|
+
JSON.stringify({ alertType: alert.alert_type, severity: alert.severity }),
|
|
232
|
+
null, // message_id
|
|
233
|
+
errorMessage
|
|
234
|
+
]);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.error('[AlertNotifier] Failed to log notification:', error);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Send critical alert immediately (bypass preferences for critical issues)
|
|
242
|
+
*
|
|
243
|
+
* @param {Object} alert - Alert object
|
|
244
|
+
* @param {string} managerEmail - Manager email to notify
|
|
245
|
+
* @returns {Promise<Object>} Send result
|
|
246
|
+
*/
|
|
247
|
+
async sendCriticalAlert(alert, managerEmail) {
|
|
248
|
+
// For critical alerts, always send both email and Slack
|
|
249
|
+
const preferences = {
|
|
250
|
+
email_enabled: true,
|
|
251
|
+
slack_enabled: true,
|
|
252
|
+
notification_types: {
|
|
253
|
+
team_alert: { email: true, slack: true }
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
return this.notificationService.sendNotification({
|
|
258
|
+
type: 'team_alert',
|
|
259
|
+
email: managerEmail,
|
|
260
|
+
preferences: preferences,
|
|
261
|
+
data: {
|
|
262
|
+
alertId: alert.alert_id,
|
|
263
|
+
alertType: alert.alert_type,
|
|
264
|
+
severity: 'critical',
|
|
265
|
+
userName: alert.user_name || alert.email_address,
|
|
266
|
+
details: alert.details,
|
|
267
|
+
message: this.getAlertMessage(alert.alert_type, alert.details),
|
|
268
|
+
referenceType: 'attention_alert',
|
|
269
|
+
referenceId: String(alert.alert_id)
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Send daily digest of alerts to managers
|
|
276
|
+
*
|
|
277
|
+
* @param {string} companyId - Company ID
|
|
278
|
+
* @returns {Promise<Object>} Send results
|
|
279
|
+
*/
|
|
280
|
+
async sendAlertDigest(companyId) {
|
|
281
|
+
const executeQuery = getExecuteQuery();
|
|
282
|
+
|
|
283
|
+
// Get active alerts for the company
|
|
284
|
+
const alerts = await executeQuery(`
|
|
285
|
+
SELECT
|
|
286
|
+
aa.alert_id,
|
|
287
|
+
aa.email_address,
|
|
288
|
+
u."User_Display_Name" as user_name,
|
|
289
|
+
aa.alert_type,
|
|
290
|
+
aa.severity,
|
|
291
|
+
aa.details,
|
|
292
|
+
aa.created_at
|
|
293
|
+
FROM rapport.attention_alerts aa
|
|
294
|
+
JOIN "Users" u ON aa.email_address = u."Email_Address"
|
|
295
|
+
WHERE aa.company_id = $1
|
|
296
|
+
AND aa.status = 'active'
|
|
297
|
+
ORDER BY
|
|
298
|
+
CASE aa.severity WHEN 'concern' THEN 1 WHEN 'warning' THEN 2 ELSE 3 END,
|
|
299
|
+
aa.created_at DESC
|
|
300
|
+
LIMIT 10
|
|
301
|
+
`, [companyId]);
|
|
302
|
+
|
|
303
|
+
if (alerts.rows.length === 0) {
|
|
304
|
+
return { sent: false, reason: 'no_active_alerts' };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Get managers for this company
|
|
308
|
+
const managers = await this.getCompanyManagers(companyId);
|
|
309
|
+
|
|
310
|
+
const results = {
|
|
311
|
+
sent: 0,
|
|
312
|
+
failed: 0
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
for (const manager of managers) {
|
|
316
|
+
const preferences = await this.getUserPreferences(manager.email_address);
|
|
317
|
+
|
|
318
|
+
// Build digest data
|
|
319
|
+
const digestData = {
|
|
320
|
+
companyId: companyId,
|
|
321
|
+
alertCount: alerts.rows.length,
|
|
322
|
+
alerts: alerts.rows.map(a => ({
|
|
323
|
+
userName: a.user_name,
|
|
324
|
+
alertType: a.alert_type,
|
|
325
|
+
severity: a.severity,
|
|
326
|
+
message: this.getAlertMessage(a.alert_type, a.details),
|
|
327
|
+
createdAt: a.created_at
|
|
328
|
+
})),
|
|
329
|
+
concernCount: alerts.rows.filter(a => a.severity === 'concern').length,
|
|
330
|
+
warningCount: alerts.rows.filter(a => a.severity === 'warning').length
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
// Use weekly_digest type but with alert data
|
|
335
|
+
await this.notificationService.sendNotification({
|
|
336
|
+
type: 'weekly_digest',
|
|
337
|
+
email: manager.email_address,
|
|
338
|
+
preferences: preferences,
|
|
339
|
+
data: {
|
|
340
|
+
userName: manager.user_name || manager.email_address,
|
|
341
|
+
weekStart: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toLocaleDateString(),
|
|
342
|
+
weekEnd: new Date().toLocaleDateString(),
|
|
343
|
+
teamActivity: {
|
|
344
|
+
activeAlerts: digestData.alertCount,
|
|
345
|
+
concernAlerts: digestData.concernCount,
|
|
346
|
+
warningAlerts: digestData.warningCount
|
|
347
|
+
},
|
|
348
|
+
alerts: digestData.alerts
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
results.sent++;
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.error(`[AlertNotifier] Failed to send digest to ${manager.email_address}:`, error);
|
|
355
|
+
results.failed++;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return results;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
module.exports = { AlertNotifier, setExecuteQuery };
|