@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,510 +0,0 @@
1
- /**
2
- * Analyze Git History Scheduled Job
3
- * Extracts BluOptima-style metrics from git repositories
4
- *
5
- * Schedule: Daily at 2am (or triggered via API)
6
- * Auth: None (Lambda scheduled event)
7
- *
8
- * Features:
9
- * - Commit frequency and volume metrics
10
- * - Working pattern analysis (time of day, day of week)
11
- * - File churn and bus factor detection
12
- * - Standards compliance scanning
13
- */
14
-
15
- const { wrapHandler, executeQuery, createSuccessResponse } = require('./helpers');
16
- const { execSync } = require('child_process');
17
- const fs = require('fs');
18
- const path = require('path');
19
-
20
- /**
21
- * Main handler - wrapped with wrapHandler for consistent error handling
22
- */
23
- exports.handler = wrapHandler(async (event, context) => {
24
- // Get all repositories that need analysis
25
- const repos = await getRepositoriesToAnalyze();
26
-
27
- if (repos.length === 0) {
28
- return createSuccessResponse({ repositories_analyzed: 0 }, 'No repositories to analyze');
29
- }
30
-
31
- const results = [];
32
- for (const repo of repos) {
33
- try {
34
- const result = await analyzeRepository(repo);
35
- results.push({ repo_id: repo.repo_id, success: true, ...result });
36
- } catch (error) {
37
- results.push({ repo_id: repo.repo_id, success: false, error: error.message });
38
- }
39
- }
40
-
41
- return createSuccessResponse({
42
- repositories_analyzed: results.length,
43
- results
44
- }, 'Git history analysis complete');
45
- });
46
-
47
- /**
48
- * Get repositories due for analysis
49
- */
50
- async function getRepositoriesToAnalyze() {
51
- const result = await executeQuery(`
52
- SELECT repo_id, Company_ID, repo_url, repo_name, default_branch,
53
- clone_path, last_commit_sha, settings
54
- FROM rapport.git_repositories
55
- WHERE last_analyzed_at IS NULL
56
- OR last_analyzed_at < NOW() - INTERVAL '1 day'
57
- ORDER BY last_analyzed_at ASC NULLS FIRST
58
- LIMIT 10
59
- `);
60
- return result.rows;
61
- }
62
-
63
- /**
64
- * Analyze a single repository
65
- */
66
- async function analyzeRepository(repo) {
67
- const periodEnd = new Date();
68
- const periodStart = new Date();
69
- periodStart.setDate(periodStart.getDate() - 7); // Last 7 days
70
-
71
- // Get git log data
72
- const gitLog = await fetchGitLog(repo, periodStart);
73
-
74
- if (!gitLog || gitLog.length === 0) {
75
- return { commits: 0, developers: 0 };
76
- }
77
-
78
- // Parse and aggregate metrics
79
- const developerMetrics = aggregateDeveloperMetrics(gitLog, periodStart, periodEnd);
80
- const fileMetrics = aggregateFileMetrics(gitLog, periodStart, periodEnd);
81
- const workingPatterns = analyzeWorkingPatterns(gitLog, periodStart, periodEnd);
82
-
83
- // Store metrics
84
- await storeDeveloperMetrics(repo.repo_id, developerMetrics);
85
- await storeFileMetrics(repo.repo_id, fileMetrics);
86
- await storeWorkingPatterns(repo.repo_id, workingPatterns);
87
-
88
- // Scan for standards compliance
89
- const complianceResults = await scanForCompliance(repo, gitLog);
90
-
91
- // Update bus factor analysis
92
- await updateBusFactor(repo.repo_id);
93
-
94
- // Mark repository as analyzed
95
- await executeQuery(`
96
- UPDATE rapport.git_repositories
97
- SET last_analyzed_at = NOW(),
98
- last_commit_sha = $2
99
- WHERE repo_id = $1
100
- `, [repo.repo_id, gitLog[0]?.sha || repo.last_commit_sha]);
101
-
102
- return {
103
- commits: gitLog.length,
104
- developers: Object.keys(developerMetrics).length,
105
- files: Object.keys(fileMetrics).length,
106
- compliance: complianceResults
107
- };
108
- }
109
-
110
- /**
111
- * Fetch git log via GitHub API or local clone
112
- * Returns array of commit objects
113
- */
114
- async function fetchGitLog(repo, since) {
115
- // For now, use GitHub API. In production, could use local clone for speed
116
- const sinceDate = since.toISOString();
117
-
118
- // Parse owner/repo from URL
119
- const match = repo.repo_url.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
120
- if (!match) {
121
- console.warn(`Cannot parse GitHub URL: ${repo.repo_url}`);
122
- return [];
123
- }
124
-
125
- const [, owner, repoName] = match;
126
-
127
- // Use GitHub API via https
128
- const https = require('https');
129
-
130
- return new Promise((resolve, reject) => {
131
- const options = {
132
- hostname: 'api.github.com',
133
- path: `/repos/${owner}/${repoName}/commits?since=${sinceDate}&per_page=100`,
134
- method: 'GET',
135
- headers: {
136
- 'User-Agent': 'MindMeld-GitAnalyzer/1.0',
137
- 'Accept': 'application/vnd.github.v3+json'
138
- },
139
- timeout: 30000
140
- };
141
-
142
- // Add auth if available
143
- if (process.env.GITHUB_TOKEN) {
144
- options.headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`;
145
- }
146
-
147
- const req = https.request(options, (res) => {
148
- let data = '';
149
- res.on('data', chunk => data += chunk);
150
- res.on('end', () => {
151
- try {
152
- if (res.statusCode !== 200) {
153
- console.warn(`GitHub API returned ${res.statusCode}: ${data}`);
154
- resolve([]);
155
- return;
156
- }
157
- const commits = JSON.parse(data);
158
- resolve(commits.map(c => ({
159
- sha: c.sha,
160
- message: c.commit.message,
161
- author_email: c.commit.author.email,
162
- author_name: c.commit.author.name,
163
- committed_at: new Date(c.commit.author.date),
164
- // Note: file stats require separate API call per commit
165
- files: []
166
- })));
167
- } catch (e) {
168
- reject(e);
169
- }
170
- });
171
- });
172
-
173
- req.on('error', reject);
174
- req.on('timeout', () => {
175
- req.destroy();
176
- reject(new Error('GitHub API timeout'));
177
- });
178
-
179
- req.end();
180
- });
181
- }
182
-
183
- /**
184
- * Aggregate developer metrics from git log
185
- */
186
- function aggregateDeveloperMetrics(gitLog, periodStart, periodEnd) {
187
- const metrics = {};
188
-
189
- for (const commit of gitLog) {
190
- const email = commit.author_email;
191
-
192
- if (!metrics[email]) {
193
- metrics[email] = {
194
- developer_email: email,
195
- developer_name: commit.author_name,
196
- period_start: periodStart,
197
- period_end: periodEnd,
198
- commit_count: 0,
199
- lines_added: 0,
200
- lines_removed: 0,
201
- files_changed: 0,
202
- active_days: new Set(),
203
- commit_times: [],
204
- first_commit_time: null,
205
- last_commit_time: null
206
- };
207
- }
208
-
209
- const m = metrics[email];
210
- m.commit_count++;
211
-
212
- // Track active days
213
- const dateStr = commit.committed_at.toISOString().split('T')[0];
214
- m.active_days.add(dateStr);
215
-
216
- // Track commit times
217
- m.commit_times.push(commit.committed_at);
218
-
219
- // Track first/last commit times
220
- const timeStr = commit.committed_at.toTimeString().split(' ')[0];
221
- if (!m.first_commit_time || commit.committed_at < new Date(`1970-01-01T${m.first_commit_time}`)) {
222
- m.first_commit_time = timeStr;
223
- }
224
- if (!m.last_commit_time || commit.committed_at > new Date(`1970-01-01T${m.last_commit_time}`)) {
225
- m.last_commit_time = timeStr;
226
- }
227
-
228
- // Aggregate file stats (if available)
229
- if (commit.files) {
230
- m.files_changed += commit.files.length;
231
- for (const file of commit.files) {
232
- m.lines_added += file.additions || 0;
233
- m.lines_removed += file.deletions || 0;
234
- }
235
- }
236
- }
237
-
238
- // Finalize metrics
239
- for (const email in metrics) {
240
- const m = metrics[email];
241
- m.active_days = m.active_days.size;
242
- m.avg_commits_per_active_day = m.active_days > 0
243
- ? (m.commit_count / m.active_days).toFixed(2)
244
- : 0;
245
- delete m.commit_times; // Don't store raw times
246
- }
247
-
248
- return metrics;
249
- }
250
-
251
- /**
252
- * Aggregate file metrics for churn analysis
253
- */
254
- function aggregateFileMetrics(gitLog, periodStart, periodEnd) {
255
- const metrics = {};
256
-
257
- for (const commit of gitLog) {
258
- if (!commit.files) continue;
259
-
260
- for (const file of commit.files) {
261
- const filePath = file.filename || file.path;
262
- if (!filePath) continue;
263
-
264
- if (!metrics[filePath]) {
265
- metrics[filePath] = {
266
- file_path: filePath,
267
- period_start: periodStart,
268
- period_end: periodEnd,
269
- change_count: 0,
270
- lines_added: 0,
271
- lines_removed: 0,
272
- contributors: new Set(),
273
- last_modified_by: null,
274
- last_modified_at: null
275
- };
276
- }
277
-
278
- const m = metrics[filePath];
279
- m.change_count++;
280
- m.lines_added += file.additions || 0;
281
- m.lines_removed += file.deletions || 0;
282
- m.contributors.add(commit.author_email);
283
- m.last_modified_by = commit.author_email;
284
- m.last_modified_at = commit.committed_at;
285
- }
286
- }
287
-
288
- // Finalize
289
- for (const filePath in metrics) {
290
- const m = metrics[filePath];
291
- m.unique_contributors = m.contributors.size;
292
- m.is_single_contributor = m.unique_contributors === 1;
293
- m.bus_factor = m.unique_contributors;
294
- delete m.contributors;
295
- }
296
-
297
- return metrics;
298
- }
299
-
300
- /**
301
- * Analyze working patterns (time of day, day of week)
302
- */
303
- function analyzeWorkingPatterns(gitLog, periodStart, periodEnd) {
304
- const patterns = {};
305
-
306
- for (const commit of gitLog) {
307
- const email = commit.author_email;
308
-
309
- if (!patterns[email]) {
310
- patterns[email] = {
311
- developer_email: email,
312
- period_start: periodStart,
313
- period_end: periodEnd,
314
- commits_by_hour: {},
315
- commits_by_day: {},
316
- weekend_commits: 0,
317
- after_hours_commits: 0
318
- };
319
- }
320
-
321
- const p = patterns[email];
322
- const hour = commit.committed_at.getHours();
323
- const day = commit.committed_at.getDay();
324
-
325
- // Count by hour
326
- p.commits_by_hour[hour] = (p.commits_by_hour[hour] || 0) + 1;
327
-
328
- // Count by day
329
- p.commits_by_day[day] = (p.commits_by_day[day] || 0) + 1;
330
-
331
- // Weekend commits (Sat=6, Sun=0)
332
- if (day === 0 || day === 6) {
333
- p.weekend_commits++;
334
- }
335
-
336
- // After hours (before 9am or after 6pm)
337
- if (hour < 9 || hour >= 18) {
338
- p.after_hours_commits++;
339
- }
340
- }
341
-
342
- // Calculate peak hour/day
343
- for (const email in patterns) {
344
- const p = patterns[email];
345
- p.peak_hour = findPeak(p.commits_by_hour);
346
- p.peak_day = findPeak(p.commits_by_day);
347
- }
348
-
349
- return patterns;
350
- }
351
-
352
- function findPeak(obj) {
353
- let maxKey = null;
354
- let maxVal = 0;
355
- for (const key in obj) {
356
- if (obj[key] > maxVal) {
357
- maxVal = obj[key];
358
- maxKey = parseInt(key);
359
- }
360
- }
361
- return maxKey;
362
- }
363
-
364
- /**
365
- * Store developer metrics in database
366
- */
367
- async function storeDeveloperMetrics(repoId, metrics) {
368
- for (const email in metrics) {
369
- const m = metrics[email];
370
- await executeQuery(`
371
- INSERT INTO rapport.developer_metrics (
372
- repo_id, developer_email, developer_name,
373
- period_start, period_end,
374
- commit_count, lines_added, lines_removed, files_changed,
375
- first_commit_time, last_commit_time,
376
- active_days, avg_commits_per_active_day
377
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
378
- ON CONFLICT (repo_id, developer_email, period_start, period_end)
379
- DO UPDATE SET
380
- commit_count = EXCLUDED.commit_count,
381
- lines_added = EXCLUDED.lines_added,
382
- lines_removed = EXCLUDED.lines_removed,
383
- files_changed = EXCLUDED.files_changed,
384
- first_commit_time = EXCLUDED.first_commit_time,
385
- last_commit_time = EXCLUDED.last_commit_time,
386
- active_days = EXCLUDED.active_days,
387
- avg_commits_per_active_day = EXCLUDED.avg_commits_per_active_day
388
- `, [
389
- repoId, m.developer_email, m.developer_name,
390
- m.period_start, m.period_end,
391
- m.commit_count, m.lines_added, m.lines_removed, m.files_changed,
392
- m.first_commit_time, m.last_commit_time,
393
- m.active_days, m.avg_commits_per_active_day
394
- ]);
395
- }
396
- }
397
-
398
- /**
399
- * Store file metrics
400
- */
401
- async function storeFileMetrics(repoId, metrics) {
402
- for (const filePath in metrics) {
403
- const m = metrics[filePath];
404
- await executeQuery(`
405
- INSERT INTO rapport.file_metrics (
406
- repo_id, file_path, period_start, period_end,
407
- change_count, lines_added, lines_removed,
408
- unique_contributors, is_single_contributor, bus_factor,
409
- last_modified_by, last_modified_at
410
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
411
- ON CONFLICT (repo_id, file_path, period_start, period_end)
412
- DO UPDATE SET
413
- change_count = EXCLUDED.change_count,
414
- lines_added = EXCLUDED.lines_added,
415
- lines_removed = EXCLUDED.lines_removed,
416
- unique_contributors = EXCLUDED.unique_contributors,
417
- is_single_contributor = EXCLUDED.is_single_contributor,
418
- bus_factor = EXCLUDED.bus_factor,
419
- last_modified_by = EXCLUDED.last_modified_by,
420
- last_modified_at = EXCLUDED.last_modified_at
421
- `, [
422
- repoId, m.file_path, m.period_start, m.period_end,
423
- m.change_count, m.lines_added, m.lines_removed,
424
- m.unique_contributors, m.is_single_contributor, m.bus_factor,
425
- m.last_modified_by, m.last_modified_at
426
- ]);
427
- }
428
- }
429
-
430
- /**
431
- * Store working patterns
432
- */
433
- async function storeWorkingPatterns(repoId, patterns) {
434
- for (const email in patterns) {
435
- const p = patterns[email];
436
- await executeQuery(`
437
- INSERT INTO rapport.working_patterns (
438
- repo_id, developer_email, period_start, period_end,
439
- commits_by_hour, commits_by_day,
440
- peak_hour, peak_day,
441
- weekend_commits, after_hours_commits
442
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
443
- ON CONFLICT (repo_id, developer_email, period_start, period_end)
444
- DO UPDATE SET
445
- commits_by_hour = EXCLUDED.commits_by_hour,
446
- commits_by_day = EXCLUDED.commits_by_day,
447
- peak_hour = EXCLUDED.peak_hour,
448
- peak_day = EXCLUDED.peak_day,
449
- weekend_commits = EXCLUDED.weekend_commits,
450
- after_hours_commits = EXCLUDED.after_hours_commits
451
- `, [
452
- repoId, p.developer_email, p.period_start, p.period_end,
453
- JSON.stringify(p.commits_by_hour), JSON.stringify(p.commits_by_day),
454
- p.peak_hour, p.peak_day,
455
- p.weekend_commits, p.after_hours_commits
456
- ]);
457
- }
458
- }
459
-
460
- /**
461
- * Scan commits for standards compliance
462
- */
463
- async function scanForCompliance(repo, gitLog) {
464
- // Get patterns to scan for
465
- const patternsResult = await executeQuery(`
466
- SELECT pattern_id, pattern_name, pattern_type, language, regex_pattern, severity
467
- FROM rapport.code_patterns
468
- WHERE enabled = true
469
- `);
470
- const patterns = patternsResult.rows;
471
-
472
- // This would need file content - for now, return placeholder
473
- // In production, fetch file content via GitHub API or local clone
474
- return {
475
- scanned: false,
476
- reason: 'File content scanning requires GitHub token with contents permission'
477
- };
478
- }
479
-
480
- /**
481
- * Update bus factor analysis
482
- */
483
- async function updateBusFactor(repoId) {
484
- await executeQuery(`
485
- INSERT INTO rapport.knowledge_silos (repo_id, module_path, primary_contributor, contribution_percentage, total_contributors, risk_level)
486
- SELECT
487
- $1,
488
- SUBSTRING(file_path FROM '^[^/]+'),
489
- last_modified_by,
490
- 100.0 / NULLIF(unique_contributors, 0),
491
- unique_contributors,
492
- CASE
493
- WHEN unique_contributors = 1 THEN 'critical'
494
- WHEN unique_contributors = 2 THEN 'high'
495
- WHEN unique_contributors <= 3 THEN 'medium'
496
- ELSE 'low'
497
- END
498
- FROM rapport.file_metrics
499
- WHERE repo_id = $1
500
- AND is_single_contributor = true
501
- ON CONFLICT (repo_id, module_path) DO UPDATE SET
502
- primary_contributor = EXCLUDED.primary_contributor,
503
- contribution_percentage = EXCLUDED.contribution_percentage,
504
- total_contributors = EXCLUDED.total_contributors,
505
- risk_level = EXCLUDED.risk_level,
506
- last_analyzed_at = NOW()
507
- `, [repoId]);
508
- }
509
-
510
- // Removed manual success/failure helpers - using wrapHandler pattern
@@ -1,135 +0,0 @@
1
- /**
2
- * Generate Alerts Scheduled Job
3
- * Generates attention alerts for developers who may need support
4
- *
5
- * Schedule: Every hour
6
- * Auth: None (Lambda scheduled event)
7
- *
8
- * Alert Types:
9
- * - stale_commits: No commits in X days
10
- * - low_conversion: Low session-to-commit conversion rate
11
- * - no_ai_usage: Active committer not using AI assistance
12
- * - high_violation_rate: Developer frequently violating standards
13
- * - stalled_patterns: Developer's patterns not maturing/being used
14
- * - declining_activity: Activity trending downward
15
- */
16
-
17
- const { wrapHandler, createSuccessResponse, executeQuery } = require('./helpers');
18
- const { AlertEngine, setExecuteQuery } = require('./core/AlertEngine');
19
-
20
- // Initialize AlertEngine with database connection
21
- setExecuteQuery(executeQuery);
22
-
23
- // Configuration can be overridden via environment variables
24
- const getThresholdConfig = () => {
25
- const config = {};
26
-
27
- // Stale commits config
28
- if (process.env.ALERT_STALE_COMMITS_CRITICAL_DAYS) {
29
- config.staleCommits = config.staleCommits || {};
30
- config.staleCommits.criticalDays = parseInt(process.env.ALERT_STALE_COMMITS_CRITICAL_DAYS);
31
- }
32
- if (process.env.ALERT_STALE_COMMITS_WARNING_DAYS) {
33
- config.staleCommits = config.staleCommits || {};
34
- config.staleCommits.warningDays = parseInt(process.env.ALERT_STALE_COMMITS_WARNING_DAYS);
35
- }
36
- if (process.env.ALERT_STALE_COMMITS_INFO_DAYS) {
37
- config.staleCommits = config.staleCommits || {};
38
- config.staleCommits.infoDays = parseInt(process.env.ALERT_STALE_COMMITS_INFO_DAYS);
39
- }
40
-
41
- // Low conversion config
42
- if (process.env.ALERT_LOW_CONVERSION_MIN_SESSIONS) {
43
- config.lowConversion = config.lowConversion || {};
44
- config.lowConversion.minSessions = parseInt(process.env.ALERT_LOW_CONVERSION_MIN_SESSIONS);
45
- }
46
- if (process.env.ALERT_LOW_CONVERSION_THRESHOLD) {
47
- config.lowConversion = config.lowConversion || {};
48
- config.lowConversion.infoThreshold = parseInt(process.env.ALERT_LOW_CONVERSION_THRESHOLD);
49
- }
50
-
51
- // High violation config
52
- if (process.env.ALERT_VIOLATION_RATE_CRITICAL) {
53
- config.highViolationRate = config.highViolationRate || {};
54
- config.highViolationRate.criticalThreshold = parseInt(process.env.ALERT_VIOLATION_RATE_CRITICAL);
55
- }
56
- if (process.env.ALERT_VIOLATION_RATE_WARNING) {
57
- config.highViolationRate = config.highViolationRate || {};
58
- config.highViolationRate.warningThreshold = parseInt(process.env.ALERT_VIOLATION_RATE_WARNING);
59
- }
60
-
61
- // Aggregation config
62
- if (process.env.ALERT_COOLDOWN_HOURS) {
63
- config.aggregation = config.aggregation || {};
64
- config.aggregation.cooldownHours = parseInt(process.env.ALERT_COOLDOWN_HOURS);
65
- }
66
- if (process.env.ALERT_EXPIRATION_DAYS) {
67
- config.aggregation = config.aggregation || {};
68
- config.aggregation.expirationDays = parseInt(process.env.ALERT_EXPIRATION_DAYS);
69
- }
70
-
71
- // Feature flags to disable specific alert types
72
- if (process.env.ALERT_DISABLE_STALE_COMMITS === 'true') {
73
- config.staleCommits = config.staleCommits || {};
74
- config.staleCommits.enabled = false;
75
- }
76
- if (process.env.ALERT_DISABLE_LOW_CONVERSION === 'true') {
77
- config.lowConversion = config.lowConversion || {};
78
- config.lowConversion.enabled = false;
79
- }
80
- if (process.env.ALERT_DISABLE_NO_AI_USAGE === 'true') {
81
- config.noAiUsage = config.noAiUsage || {};
82
- config.noAiUsage.enabled = false;
83
- }
84
- if (process.env.ALERT_DISABLE_VIOLATION_RATE === 'true') {
85
- config.highViolationRate = config.highViolationRate || {};
86
- config.highViolationRate.enabled = false;
87
- }
88
- if (process.env.ALERT_DISABLE_STALLED_PATTERNS === 'true') {
89
- config.stalledPatterns = config.stalledPatterns || {};
90
- config.stalledPatterns.enabled = false;
91
- }
92
- if (process.env.ALERT_DISABLE_DECLINING_ACTIVITY === 'true') {
93
- config.decliningActivity = config.decliningActivity || {};
94
- config.decliningActivity.enabled = false;
95
- }
96
-
97
- return config;
98
- };
99
-
100
- exports.handler = wrapHandler(async (event, context) => {
101
- console.log('[GenerateAlerts] Starting scheduled alert generation');
102
-
103
- // Get optional company filter from event (for testing or targeted runs)
104
- const companyId = event?.companyId || null;
105
- const dryRun = event?.dryRun || process.env.ALERT_DRY_RUN === 'true';
106
-
107
- // Initialize AlertEngine with configuration
108
- const thresholds = getThresholdConfig();
109
- const alertEngine = new AlertEngine({
110
- thresholds,
111
- dryRun
112
- });
113
-
114
- // Log configuration for debugging
115
- console.log('[GenerateAlerts] Configuration:', {
116
- companyId: companyId || 'all',
117
- dryRun,
118
- thresholdsOverride: Object.keys(thresholds).length > 0 ? thresholds : 'defaults'
119
- });
120
-
121
- // Generate alerts
122
- const summary = await alertEngine.generateAlerts(companyId);
123
-
124
- console.log('[GenerateAlerts] Completed:', summary);
125
-
126
- return createSuccessResponse({
127
- alerts_created: summary.alertsCreated,
128
- alerts_expired: summary.alertsExpired,
129
- by_type: summary.byType,
130
- errors: summary.errors,
131
- started_at: summary.startedAt,
132
- completed_at: summary.completedAt,
133
- dry_run: dryRun
134
- }, 'Alerts generated successfully');
135
- });