@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.
- package/hooks/session-start.js +312 -85
- package/package.json +21 -13
- package/scripts/init-project.js +9 -23
- package/scripts/repo-analyzer.js +118 -2
- package/src/client/dbShim.js +16 -0
- package/src/core/AuthManager.js +3 -2
- package/src/handlers/helpers/dbOperations.js +9 -46
- package/src/index.js +2 -217
- package/src/utils/piiMask.js +16 -0
- package/scripts/inject.js +0 -409
- package/scripts/mcp-bridge.js +0 -220
- package/scripts/standards.js +0 -285
- package/src/collaboration/CollaborationPrompt.js +0 -460
- package/src/core/AlertEngine.js +0 -813
- package/src/core/AlertNotifier.js +0 -363
- package/src/core/CorrelationAnalyzer.js +0 -931
- package/src/core/CrossReferenceEngine.js +0 -624
- package/src/core/CurationEngine.js +0 -688
- package/src/core/DeprecationScheduler.js +0 -183
- package/src/core/LoadBearingDetector.js +0 -242
- package/src/core/NotificationService.js +0 -1032
- package/src/core/RapportOrchestrator.js +0 -632
- package/src/core/RelevanceDetector.js +0 -694
- package/src/core/StandardLifecycle.js +0 -244
- package/src/core/StandardsIngestion.js +0 -991
- package/src/core/TeamLoadBearingDetector.js +0 -431
- package/src/core/parsers/adrParser.js +0 -479
- package/src/core/parsers/cursorRulesParser.js +0 -564
- package/src/core/parsers/eslintParser.js +0 -439
- package/src/database/dbOperations.js +0 -105
- package/src/handlers/activity/activityGetMe.js +0 -98
- package/src/handlers/activity/activityGetTeam.js +0 -175
- package/src/handlers/admin/adminSetup.js +0 -216
- package/src/handlers/alerts/alertsAcknowledge.js +0 -92
- package/src/handlers/alerts/alertsGet.js +0 -250
- package/src/handlers/analytics/activitySummaryGet.js +0 -234
- package/src/handlers/analytics/coachingGet.js +0 -361
- package/src/handlers/analytics/convergenceGet.js +0 -236
- package/src/handlers/analytics/developerScoreGet.js +0 -137
- package/src/handlers/collaborators/collaboratorAdd.js +0 -200
- package/src/handlers/collaborators/collaboratorInvite.js +0 -219
- package/src/handlers/collaborators/collaboratorList.js +0 -82
- package/src/handlers/collaborators/collaboratorRemove.js +0 -128
- package/src/handlers/collaborators/inviteAccept.js +0 -122
- package/src/handlers/company/companyUsersDelete.js +0 -141
- package/src/handlers/company/companyUsersGet.js +0 -90
- package/src/handlers/company/companyUsersPost.js +0 -267
- package/src/handlers/company/companyUsersPut.js +0 -76
- package/src/handlers/context/contextGet.js +0 -57
- package/src/handlers/context/invariantsGet.js +0 -74
- package/src/handlers/context/loopsGet.js +0 -82
- package/src/handlers/context/notesCreate.js +0 -74
- package/src/handlers/context/purposeGet.js +0 -78
- package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
- package/src/handlers/correlations/correlationsGet.js +0 -93
- package/src/handlers/correlations/correlationsProjectGet.js +0 -153
- package/src/handlers/enterprise/controlTowerGet.js +0 -224
- package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
- package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
- package/src/handlers/github/githubConnectionStatus.js +0 -49
- package/src/handlers/github/githubDiscoverPatterns.js +0 -621
- package/src/handlers/github/githubOAuthCallback.js +0 -178
- package/src/handlers/github/githubOAuthStart.js +0 -59
- package/src/handlers/github/githubPatternsReview.js +0 -76
- package/src/handlers/github/githubReposList.js +0 -105
- package/src/handlers/health/healthGet.js +0 -55
- package/src/handlers/helpers/auditLogger.js +0 -201
- package/src/handlers/helpers/checkSuperAdmin.js +0 -84
- package/src/handlers/helpers/decisionFrames.js +0 -29
- package/src/handlers/helpers/errorHandler.js +0 -49
- package/src/handlers/helpers/index.js +0 -138
- package/src/handlers/helpers/lambdaWrapper.js +0 -60
- package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
- package/src/handlers/helpers/predictiveCache.js +0 -51
- package/src/handlers/helpers/projectAccess.js +0 -88
- package/src/handlers/helpers/responseUtil.js +0 -55
- package/src/handlers/helpers/subscriptionTiers.js +0 -1168
- package/src/handlers/mcp/mcpHandler.js +0 -569
- package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
- package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
- package/src/handlers/notifications/getPreferences.js +0 -84
- package/src/handlers/notifications/sendNotification.js +0 -170
- package/src/handlers/notifications/updatePreferences.js +0 -316
- package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
- package/src/handlers/patterns/patternUsagePost.js +0 -182
- package/src/handlers/patterns/patternViolationPost.js +0 -185
- package/src/handlers/projects/projectCreate.js +0 -248
- package/src/handlers/projects/projectDelete.js +0 -82
- package/src/handlers/projects/projectGet.js +0 -95
- package/src/handlers/projects/projectUpdate.js +0 -117
- package/src/handlers/reports/aiLeverage.js +0 -210
- package/src/handlers/reports/engineeringInvestment.js +0 -132
- package/src/handlers/reports/riskForecast.js +0 -206
- package/src/handlers/reports/standardsRoi.js +0 -254
- package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
- package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
- package/src/handlers/scheduled/generateAlerts.js +0 -135
- package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
- package/src/handlers/scheduled/refreshActivity.js +0 -21
- package/src/handlers/scheduled/scanCompliance.js +0 -334
- package/src/handlers/sessions/sessionEndPost.js +0 -180
- package/src/handlers/sessions/sessionStandardsPost.js +0 -171
- package/src/handlers/standards/catalogGet.js +0 -185
- package/src/handlers/standards/catalogSync.js +0 -120
- package/src/handlers/standards/discoveriesGet.js +0 -89
- package/src/handlers/standards/projectStandardsGet.js +0 -129
- package/src/handlers/standards/projectStandardsPut.js +0 -151
- package/src/handlers/standards/standardsAuditGet.js +0 -65
- package/src/handlers/standards/standardsParseUpload.js +0 -149
- package/src/handlers/standards/standardsRelevantPost.js +0 -405
- package/src/handlers/standards/standardsTransition.js +0 -161
- package/src/handlers/stripe/addonManagePost.js +0 -240
- package/src/handlers/stripe/billingPortalPost.js +0 -93
- package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
- package/src/handlers/stripe/seatsUpdatePost.js +0 -185
- package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
- package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
- package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
- package/src/handlers/stripe/webhookPost.js +0 -482
- package/src/handlers/user/apiTokenCreate.js +0 -71
- package/src/handlers/user/apiTokenList.js +0 -64
- package/src/handlers/user/userSplashAck.js +0 -91
- package/src/handlers/user/userSplashGet.js +0 -211
- package/src/handlers/users/cognitoPostConfirmation.js +0 -186
- package/src/handlers/users/cognitoPreSignUp.js +0 -114
- package/src/handlers/users/userEntitlementsGet.js +0 -89
- package/src/handlers/users/userGet.js +0 -118
- package/src/handlers/users/userProfilePut.js +0 -77
- package/src/handlers/webhooks/githubWebhook.js +0 -215
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Developer Coaching Recommendations Handler
|
|
3
|
-
* LLM-powered coaching insights per developer
|
|
4
|
-
*
|
|
5
|
-
* GET /api/analytics/coaching?user_id=xxx&project_id=xxx
|
|
6
|
-
* Query params:
|
|
7
|
-
* - user_id (required) - Developer email to generate coaching for
|
|
8
|
-
* - project_id (required) - Project to scope coaching data
|
|
9
|
-
*
|
|
10
|
-
* Returns:
|
|
11
|
-
* - user_email: developer email
|
|
12
|
-
* - recommendations: LLM-generated coaching items with categories and evidence
|
|
13
|
-
* - strengths: identified developer strengths
|
|
14
|
-
* - generated_at: timestamp
|
|
15
|
-
*
|
|
16
|
-
* Auth: Cognito JWT required, Manager or Admin role (or self)
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
20
|
-
const { LLMPatternDetector } = require('./core/LLMPatternDetector');
|
|
21
|
-
|
|
22
|
-
async function getCoachingRecommendations({ requestContext, queryStringParameters }) {
|
|
23
|
-
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
24
|
-
|
|
25
|
-
if (!email) {
|
|
26
|
-
return createErrorResponse(401, 'Unauthorized');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const params = queryStringParameters || {};
|
|
30
|
-
const projectId = params.project_id;
|
|
31
|
-
const targetUserId = params.user_id;
|
|
32
|
-
|
|
33
|
-
if (!projectId) {
|
|
34
|
-
return createErrorResponse(400, 'project_id is required');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!targetUserId) {
|
|
38
|
-
return createErrorResponse(400, 'user_id is required');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Check access: self-access or manager/admin
|
|
42
|
-
const isSelf = email.toLowerCase() === targetUserId.toLowerCase();
|
|
43
|
-
|
|
44
|
-
if (!isSelf) {
|
|
45
|
-
const accessCheck = await executeQuery(`
|
|
46
|
-
SELECT ue.company_id
|
|
47
|
-
FROM rapport.user_entitlements ue
|
|
48
|
-
JOIN rapport.projects p ON p.company_id = ue.company_id
|
|
49
|
-
WHERE ue.email_address = $1
|
|
50
|
-
AND p.project_id = $2
|
|
51
|
-
AND (ue.admin = true OR ue.manager = true)
|
|
52
|
-
LIMIT 1
|
|
53
|
-
`, [email, projectId]);
|
|
54
|
-
|
|
55
|
-
if (accessCheck.rows.length === 0) {
|
|
56
|
-
return createErrorResponse(403, 'Manager or Admin access required to view other developers');
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Verify target user exists and belongs to the project's company
|
|
61
|
-
const userResult = await executeQuery(`
|
|
62
|
-
SELECT
|
|
63
|
-
u.email_address as user_email,
|
|
64
|
-
CONCAT(u.first_name, ' ', u.last_name) as display_name
|
|
65
|
-
FROM rapport.users u
|
|
66
|
-
JOIN rapport.user_entitlements ue ON ue.email_address = u.email_address
|
|
67
|
-
JOIN rapport.projects p ON p.company_id = ue.company_id
|
|
68
|
-
WHERE u.email_address = $1
|
|
69
|
-
AND p.project_id = $2
|
|
70
|
-
LIMIT 1
|
|
71
|
-
`, [targetUserId, projectId]);
|
|
72
|
-
|
|
73
|
-
if (userResult.rows.length === 0) {
|
|
74
|
-
return createErrorResponse(404, 'Developer not found in this project');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const targetUser = userResult.rows[0];
|
|
78
|
-
|
|
79
|
-
// Fetch violation data for the developer (last 90 days for comprehensive coaching)
|
|
80
|
-
const lookbackDate = new Date();
|
|
81
|
-
lookbackDate.setDate(lookbackDate.getDate() - 90);
|
|
82
|
-
|
|
83
|
-
const violationResult = await executeQuery(`
|
|
84
|
-
SELECT
|
|
85
|
-
pv.category,
|
|
86
|
-
pv.severity,
|
|
87
|
-
pv.description,
|
|
88
|
-
pv.standard_name,
|
|
89
|
-
pv.detected_at,
|
|
90
|
-
COUNT(*) OVER (PARTITION BY pv.category) as category_count
|
|
91
|
-
FROM rapport.pattern_violations pv
|
|
92
|
-
WHERE pv.developer_email = $1
|
|
93
|
-
AND pv.project_id = $2
|
|
94
|
-
AND pv.detected_at >= $3
|
|
95
|
-
ORDER BY pv.detected_at DESC
|
|
96
|
-
`, [targetUserId, projectId, lookbackDate]);
|
|
97
|
-
|
|
98
|
-
// Fetch pattern usage data
|
|
99
|
-
const patternResult = await executeQuery(`
|
|
100
|
-
SELECT
|
|
101
|
-
p.intent,
|
|
102
|
-
p.maturity,
|
|
103
|
-
COUNT(DISTINCT pu.session_id) as sessions_used,
|
|
104
|
-
MAX(pu.used_at) as last_used
|
|
105
|
-
FROM rapport.pattern_usage pu
|
|
106
|
-
JOIN rapport.patterns p ON pu.pattern_id = p.pattern_id
|
|
107
|
-
WHERE pu.email_address = $1
|
|
108
|
-
AND pu.project_id = $2
|
|
109
|
-
AND pu.used_at >= $3
|
|
110
|
-
GROUP BY p.intent, p.maturity
|
|
111
|
-
ORDER BY sessions_used DESC
|
|
112
|
-
`, [targetUserId, projectId, lookbackDate]);
|
|
113
|
-
|
|
114
|
-
// Fetch compliance metrics
|
|
115
|
-
const metricsResult = await executeQuery(`
|
|
116
|
-
SELECT
|
|
117
|
-
AVG(dm.compliance_score) as avg_compliance,
|
|
118
|
-
SUM(dm.commit_count) as total_commits,
|
|
119
|
-
SUM(dm.standards_compliant_commits) as compliant_commits
|
|
120
|
-
FROM rapport.developer_metrics dm
|
|
121
|
-
WHERE dm.developer_email = $1
|
|
122
|
-
AND dm.project_id = $2
|
|
123
|
-
AND dm.period_start >= $3
|
|
124
|
-
`, [targetUserId, projectId, lookbackDate]);
|
|
125
|
-
|
|
126
|
-
const metrics = metricsResult.rows[0] || {};
|
|
127
|
-
const violations = violationResult.rows;
|
|
128
|
-
const patterns = patternResult.rows;
|
|
129
|
-
|
|
130
|
-
// Attempt LLM-based coaching recommendations
|
|
131
|
-
let recommendations = [];
|
|
132
|
-
let strengths = [];
|
|
133
|
-
|
|
134
|
-
const detector = new LLMPatternDetector();
|
|
135
|
-
|
|
136
|
-
if (detector.isAvailable()) {
|
|
137
|
-
try {
|
|
138
|
-
const coachingResult = await generateLLMCoaching(detector, {
|
|
139
|
-
userEmail: targetUserId,
|
|
140
|
-
displayName: targetUser.display_name,
|
|
141
|
-
violations,
|
|
142
|
-
patterns,
|
|
143
|
-
metrics,
|
|
144
|
-
projectId
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
recommendations = coachingResult.recommendations || [];
|
|
148
|
-
strengths = coachingResult.strengths || [];
|
|
149
|
-
} catch (error) {
|
|
150
|
-
console.error('[CoachingGet] LLM coaching generation failed, falling back to rule-based:', error.message);
|
|
151
|
-
const fallback = generateRuleBasedCoaching(violations, patterns, metrics);
|
|
152
|
-
recommendations = fallback.recommendations;
|
|
153
|
-
strengths = fallback.strengths;
|
|
154
|
-
}
|
|
155
|
-
} else {
|
|
156
|
-
// Fallback: rule-based recommendations from violation patterns
|
|
157
|
-
const fallback = generateRuleBasedCoaching(violations, patterns, metrics);
|
|
158
|
-
recommendations = fallback.recommendations;
|
|
159
|
-
strengths = fallback.strengths;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return createSuccessResponse({
|
|
163
|
-
user_email: targetUser.user_email,
|
|
164
|
-
recommendations,
|
|
165
|
-
strengths,
|
|
166
|
-
generated_at: new Date().toISOString()
|
|
167
|
-
}, 'Coaching recommendations generated');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
exports.handler = wrapHandler(getCoachingRecommendations);
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Generate coaching recommendations using LLM
|
|
174
|
-
*/
|
|
175
|
-
async function generateLLMCoaching(detector, context) {
|
|
176
|
-
const violationSummary = summarizeViolations(context.violations);
|
|
177
|
-
const patternSummary = summarizePatterns(context.patterns);
|
|
178
|
-
const complianceAvg = parseFloat(context.metrics.avg_compliance || 0).toFixed(1);
|
|
179
|
-
|
|
180
|
-
const transcript = `
|
|
181
|
-
Developer Coaching Analysis for ${context.displayName || context.userEmail}:
|
|
182
|
-
|
|
183
|
-
COMPLIANCE METRICS:
|
|
184
|
-
- Average compliance score: ${complianceAvg}%
|
|
185
|
-
- Total commits: ${context.metrics.total_commits || 0}
|
|
186
|
-
- Standards-compliant commits: ${context.metrics.compliant_commits || 0}
|
|
187
|
-
|
|
188
|
-
VIOLATIONS (last 90 days):
|
|
189
|
-
${violationSummary}
|
|
190
|
-
|
|
191
|
-
PATTERN USAGE (last 90 days):
|
|
192
|
-
${patternSummary}
|
|
193
|
-
|
|
194
|
-
Generate coaching recommendations with:
|
|
195
|
-
1. Specific, actionable items categorized by area (security, performance, architecture, etc.)
|
|
196
|
-
2. Priority level (high, medium, low) based on violation frequency and severity
|
|
197
|
-
3. Suggested standards to review
|
|
198
|
-
4. Evidence from their actual violation and pattern data
|
|
199
|
-
5. Identified strengths based on pattern adoption and compliance
|
|
200
|
-
|
|
201
|
-
Respond with JSON:
|
|
202
|
-
{
|
|
203
|
-
"recommendations": [
|
|
204
|
-
{
|
|
205
|
-
"category": "category_name",
|
|
206
|
-
"priority": "high|medium|low",
|
|
207
|
-
"title": "Short coaching title",
|
|
208
|
-
"description": "Detailed coaching recommendation",
|
|
209
|
-
"suggested_standards": ["standard1", "standard2"],
|
|
210
|
-
"evidence": { "violations": 0, "most_recent": "date" }
|
|
211
|
-
}
|
|
212
|
-
],
|
|
213
|
-
"strengths": ["Strength 1", "Strength 2"]
|
|
214
|
-
}`;
|
|
215
|
-
|
|
216
|
-
const result = await detector.analyzeSessionTranscript(transcript, {
|
|
217
|
-
projectName: `Project ${context.projectId}`,
|
|
218
|
-
techStack: 'Node.js, AWS Lambda, PostgreSQL, React'
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
// Parse LLM response for coaching-specific format
|
|
222
|
-
if (result.success && result.recommendations) {
|
|
223
|
-
return {
|
|
224
|
-
recommendations: Array.isArray(result.recommendations)
|
|
225
|
-
? result.recommendations.map(rec => ({
|
|
226
|
-
category: rec.category || 'general',
|
|
227
|
-
priority: rec.priority || 'medium',
|
|
228
|
-
title: rec.title || rec.action || 'Recommendation',
|
|
229
|
-
description: rec.description || rec.details || '',
|
|
230
|
-
suggested_standards: rec.suggested_standards || [],
|
|
231
|
-
evidence: rec.evidence || {}
|
|
232
|
-
}))
|
|
233
|
-
: [],
|
|
234
|
-
strengths: result.strengths || []
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// If LLM returned patterns instead of coaching format, adapt
|
|
239
|
-
return generateRuleBasedCoaching(context.violations, context.patterns, context.metrics);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Generate rule-based coaching recommendations from violation patterns
|
|
244
|
-
*/
|
|
245
|
-
function generateRuleBasedCoaching(violations, patterns, metrics) {
|
|
246
|
-
const recommendations = [];
|
|
247
|
-
const strengths = [];
|
|
248
|
-
|
|
249
|
-
// Group violations by category
|
|
250
|
-
const violationsByCategory = {};
|
|
251
|
-
for (const v of violations) {
|
|
252
|
-
if (!violationsByCategory[v.category]) {
|
|
253
|
-
violationsByCategory[v.category] = [];
|
|
254
|
-
}
|
|
255
|
-
violationsByCategory[v.category].push(v);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Generate recommendations from violations
|
|
259
|
-
for (const [category, categoryViolations] of Object.entries(violationsByCategory)) {
|
|
260
|
-
const count = categoryViolations.length;
|
|
261
|
-
const mostRecent = categoryViolations[0]?.detected_at;
|
|
262
|
-
const uniqueStandards = [...new Set(categoryViolations.map(v => v.standard_name).filter(Boolean))];
|
|
263
|
-
|
|
264
|
-
let priority = 'low';
|
|
265
|
-
if (count >= 5 || categoryViolations.some(v => v.severity === 'critical')) {
|
|
266
|
-
priority = 'high';
|
|
267
|
-
} else if (count >= 2 || categoryViolations.some(v => v.severity === 'warning')) {
|
|
268
|
-
priority = 'medium';
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const titles = {
|
|
272
|
-
security: 'Improve security practices',
|
|
273
|
-
performance: 'Address performance anti-patterns',
|
|
274
|
-
database: 'Strengthen database access patterns',
|
|
275
|
-
api: 'Align with API design standards',
|
|
276
|
-
architecture: 'Follow architectural guidelines',
|
|
277
|
-
'error-handling': 'Enhance error handling consistency'
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
const descriptions = {
|
|
281
|
-
security: `This developer has ${count} security-related violation(s) in the last 90 days. Review input validation, secrets management, and authorization patterns.`,
|
|
282
|
-
performance: `${count} performance issue(s) detected. Focus on connection management, caching strategies, and avoiding runtime SSM lookups.`,
|
|
283
|
-
database: `${count} database-related violation(s) found. Ensure use of cached single client pattern and parameterized queries.`,
|
|
284
|
-
api: `${count} API design violation(s). Review response formatting, CORS configuration, and handler patterns.`,
|
|
285
|
-
architecture: `${count} architectural violation(s). Check adherence to business-scoped IDs, module boundaries, and code organization.`,
|
|
286
|
-
'error-handling': `${count} error handling issue(s). Ensure consistent use of wrapHandler and proper error response patterns.`
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
recommendations.push({
|
|
290
|
-
category,
|
|
291
|
-
priority,
|
|
292
|
-
title: titles[category] || `Improve ${category} practices`,
|
|
293
|
-
description: descriptions[category] || `This developer has ${count} violation(s) related to ${category} in the last 90 days.`,
|
|
294
|
-
suggested_standards: uniqueStandards.length > 0 ? uniqueStandards : [category],
|
|
295
|
-
evidence: {
|
|
296
|
-
violations: count,
|
|
297
|
-
most_recent: mostRecent ? new Date(mostRecent).toISOString().split('T')[0] : null
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Sort recommendations by priority
|
|
303
|
-
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
304
|
-
recommendations.sort((a, b) => (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2));
|
|
305
|
-
|
|
306
|
-
// Identify strengths from pattern usage
|
|
307
|
-
const highAdoptionPatterns = patterns.filter(p => parseInt(p.sessions_used) >= 5);
|
|
308
|
-
if (highAdoptionPatterns.length > 0) {
|
|
309
|
-
strengths.push(`Consistent use of ${highAdoptionPatterns.length} team pattern(s)`);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const validatedPatterns = patterns.filter(p => p.maturity === 'validated' || p.maturity === 'reinforced');
|
|
313
|
-
if (validatedPatterns.length > 0) {
|
|
314
|
-
strengths.push(`Active contributor to ${validatedPatterns.length} validated pattern(s)`);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const complianceScore = parseFloat(metrics.avg_compliance || 0);
|
|
318
|
-
if (complianceScore >= 80) {
|
|
319
|
-
strengths.push('Strong standards adherence above 80%');
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const totalCommits = parseInt(metrics.total_commits || 0);
|
|
323
|
-
const compliantCommits = parseInt(metrics.compliant_commits || 0);
|
|
324
|
-
if (totalCommits > 0 && (compliantCommits / totalCommits) >= 0.9) {
|
|
325
|
-
strengths.push('Over 90% of commits meet standards compliance');
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (violations.length === 0 && totalCommits > 0) {
|
|
329
|
-
strengths.push('Zero violations in the last 90 days');
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return { recommendations, strengths };
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Summarize violations for LLM context
|
|
337
|
-
*/
|
|
338
|
-
function summarizeViolations(violations) {
|
|
339
|
-
if (violations.length === 0) return 'No violations detected in the last 90 days.';
|
|
340
|
-
|
|
341
|
-
const byCategory = {};
|
|
342
|
-
for (const v of violations) {
|
|
343
|
-
if (!byCategory[v.category]) byCategory[v.category] = 0;
|
|
344
|
-
byCategory[v.category]++;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return Object.entries(byCategory)
|
|
348
|
-
.map(([cat, count]) => `- ${cat}: ${count} violation(s)`)
|
|
349
|
-
.join('\n');
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Summarize patterns for LLM context
|
|
354
|
-
*/
|
|
355
|
-
function summarizePatterns(patterns) {
|
|
356
|
-
if (patterns.length === 0) return 'No pattern usage recorded.';
|
|
357
|
-
|
|
358
|
-
return patterns.slice(0, 10)
|
|
359
|
-
.map(p => `- ${p.intent} (${p.maturity}): used in ${p.sessions_used} session(s)`)
|
|
360
|
-
.join('\n');
|
|
361
|
-
}
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Team Convergence Analytics Handler
|
|
3
|
-
* Measures how consistently team members adopt the same standards
|
|
4
|
-
*
|
|
5
|
-
* GET /api/analytics/convergence
|
|
6
|
-
* Query: ?period=30d&Company_ID=xxx
|
|
7
|
-
* Auth: Cognito JWT required, Manager or Admin role
|
|
8
|
-
*
|
|
9
|
-
* Returns convergence score, per-standard adoption rates,
|
|
10
|
-
* per-developer alignment scores, and weekly trend.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
14
|
-
|
|
15
|
-
async function getConvergence({ requestContext, queryStringParameters }) {
|
|
16
|
-
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
17
|
-
|
|
18
|
-
if (!email) {
|
|
19
|
-
return createErrorResponse(401, 'Authentication required');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const params = queryStringParameters || {};
|
|
23
|
-
const period = params.period || '30d';
|
|
24
|
-
const companyId = params.Company_ID;
|
|
25
|
-
|
|
26
|
-
// Validate manager/admin access
|
|
27
|
-
const accessCheck = await executeQuery(`
|
|
28
|
-
SELECT ue.company_id
|
|
29
|
-
FROM rapport.user_entitlements ue
|
|
30
|
-
WHERE ue.email_address = $1
|
|
31
|
-
AND (ue.admin = true OR ue.manager = true)
|
|
32
|
-
${companyId ? 'AND ue.company_id = $2' : ''}
|
|
33
|
-
LIMIT 1
|
|
34
|
-
`, companyId ? [email, companyId] : [email]);
|
|
35
|
-
|
|
36
|
-
if (accessCheck.rows.length === 0) {
|
|
37
|
-
return createErrorResponse(403, 'Manager or Admin access required for convergence analytics');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const userCompanyId = companyId || accessCheck.rows[0].company_id;
|
|
41
|
-
const periodDays = parsePeriod(period);
|
|
42
|
-
const periodStart = new Date();
|
|
43
|
-
periodStart.setDate(periodStart.getDate() - periodDays);
|
|
44
|
-
|
|
45
|
-
// Get all active developers (those with sessions in period)
|
|
46
|
-
let activeDevelopers = [];
|
|
47
|
-
try {
|
|
48
|
-
const devResult = await executeQuery(`
|
|
49
|
-
SELECT DISTINCT s.email_address
|
|
50
|
-
FROM rapport.sessions s
|
|
51
|
-
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
52
|
-
WHERE p.company_id = $1
|
|
53
|
-
AND s.started_at >= $2
|
|
54
|
-
AND s.email_address IS NOT NULL
|
|
55
|
-
`, [userCompanyId, periodStart]);
|
|
56
|
-
activeDevelopers = devResult.rows.map(r => r.email_address);
|
|
57
|
-
} catch (e) {
|
|
58
|
-
console.log('[Convergence] Active developers query failed:', e.message);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (activeDevelopers.length === 0) {
|
|
62
|
-
return createSuccessResponse({
|
|
63
|
-
convergence_score: 0,
|
|
64
|
-
trend: 'stable',
|
|
65
|
-
developer_count: 0,
|
|
66
|
-
standards_in_use: 0,
|
|
67
|
-
by_standard: [],
|
|
68
|
-
by_developer: [],
|
|
69
|
-
convergence_trend: []
|
|
70
|
-
}, 'No active developers in period');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Per-standard adoption: how many active devs received each standard
|
|
74
|
-
let byStandard = [];
|
|
75
|
-
try {
|
|
76
|
-
const standardResult = await executeQuery(`
|
|
77
|
-
SELECT
|
|
78
|
-
ss.standard_id,
|
|
79
|
-
ss.standard_name,
|
|
80
|
-
COUNT(DISTINCT s.email_address) as developers_using,
|
|
81
|
-
COUNT(*) as total_injections,
|
|
82
|
-
ROUND(AVG(ss.relevance_score), 2) as avg_relevance
|
|
83
|
-
FROM rapport.session_standards ss
|
|
84
|
-
JOIN rapport.sessions s ON ss.session_id = s.session_id
|
|
85
|
-
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
86
|
-
WHERE p.company_id = $1
|
|
87
|
-
AND ss.created_at >= $2
|
|
88
|
-
AND s.email_address IS NOT NULL
|
|
89
|
-
GROUP BY ss.standard_id, ss.standard_name
|
|
90
|
-
HAVING COUNT(DISTINCT s.email_address) >= 1
|
|
91
|
-
ORDER BY developers_using DESC, total_injections DESC
|
|
92
|
-
`, [userCompanyId, periodStart]);
|
|
93
|
-
|
|
94
|
-
byStandard = standardResult.rows.map(row => ({
|
|
95
|
-
standard_id: row.standard_id,
|
|
96
|
-
standard_name: row.standard_name,
|
|
97
|
-
adoption_rate: Math.round((parseInt(row.developers_using) / activeDevelopers.length) * 100),
|
|
98
|
-
developers_using: parseInt(row.developers_using),
|
|
99
|
-
total_injections: parseInt(row.total_injections),
|
|
100
|
-
avg_relevance: parseFloat(row.avg_relevance) || 0
|
|
101
|
-
}));
|
|
102
|
-
} catch (e) {
|
|
103
|
-
console.log('[Convergence] Per-standard query failed:', e.message);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Per-developer: how many unique standards each dev uses
|
|
107
|
-
let byDeveloper = [];
|
|
108
|
-
try {
|
|
109
|
-
const devStandardsResult = await executeQuery(`
|
|
110
|
-
SELECT
|
|
111
|
-
s.email_address,
|
|
112
|
-
COALESCE(MAX(ue.display_name), s.email_address) as display_name,
|
|
113
|
-
COUNT(DISTINCT ss.standard_id) as unique_standards,
|
|
114
|
-
COUNT(*) as total_injections
|
|
115
|
-
FROM rapport.session_standards ss
|
|
116
|
-
JOIN rapport.sessions s ON ss.session_id = s.session_id
|
|
117
|
-
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
118
|
-
LEFT JOIN rapport.user_entitlements ue ON s.email_address = ue.email_address AND ue.company_id = $1
|
|
119
|
-
WHERE p.company_id = $1
|
|
120
|
-
AND ss.created_at >= $2
|
|
121
|
-
AND s.email_address IS NOT NULL
|
|
122
|
-
GROUP BY s.email_address
|
|
123
|
-
ORDER BY unique_standards DESC
|
|
124
|
-
`, [userCompanyId, periodStart]);
|
|
125
|
-
|
|
126
|
-
const totalUniqueStandards = byStandard.length;
|
|
127
|
-
|
|
128
|
-
byDeveloper = devStandardsResult.rows.map(row => ({
|
|
129
|
-
email: row.email_address,
|
|
130
|
-
display_name: row.display_name,
|
|
131
|
-
unique_standards: parseInt(row.unique_standards),
|
|
132
|
-
total_injections: parseInt(row.total_injections),
|
|
133
|
-
alignment_score: totalUniqueStandards > 0
|
|
134
|
-
? Math.round((parseInt(row.unique_standards) / totalUniqueStandards) * 100)
|
|
135
|
-
: 0
|
|
136
|
-
}));
|
|
137
|
-
} catch (e) {
|
|
138
|
-
console.log('[Convergence] Per-developer query failed:', e.message);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Convergence score: average adoption rate across standards
|
|
142
|
-
// High score = most standards are used by most developers
|
|
143
|
-
const convergenceScore = byStandard.length > 0
|
|
144
|
-
? Math.round(byStandard.reduce((sum, s) => sum + s.adoption_rate, 0) / byStandard.length)
|
|
145
|
-
: 0;
|
|
146
|
-
|
|
147
|
-
// Weekly convergence trend (last 8 weeks or period, whichever is shorter)
|
|
148
|
-
let convergenceTrend = [];
|
|
149
|
-
try {
|
|
150
|
-
const trendResult = await executeQuery(`
|
|
151
|
-
WITH weekly_adoption AS (
|
|
152
|
-
SELECT
|
|
153
|
-
DATE_TRUNC('week', ss.created_at) as week,
|
|
154
|
-
ss.standard_id,
|
|
155
|
-
COUNT(DISTINCT s.email_address) as devs_using
|
|
156
|
-
FROM rapport.session_standards ss
|
|
157
|
-
JOIN rapport.sessions s ON ss.session_id = s.session_id
|
|
158
|
-
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
159
|
-
WHERE p.company_id = $1
|
|
160
|
-
AND ss.created_at >= $2
|
|
161
|
-
AND s.email_address IS NOT NULL
|
|
162
|
-
GROUP BY DATE_TRUNC('week', ss.created_at), ss.standard_id
|
|
163
|
-
),
|
|
164
|
-
weekly_devs AS (
|
|
165
|
-
SELECT
|
|
166
|
-
DATE_TRUNC('week', s.started_at) as week,
|
|
167
|
-
COUNT(DISTINCT s.email_address) as active_devs
|
|
168
|
-
FROM rapport.sessions s
|
|
169
|
-
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
170
|
-
WHERE p.company_id = $1
|
|
171
|
-
AND s.started_at >= $2
|
|
172
|
-
AND s.email_address IS NOT NULL
|
|
173
|
-
GROUP BY DATE_TRUNC('week', s.started_at)
|
|
174
|
-
)
|
|
175
|
-
SELECT
|
|
176
|
-
wa.week,
|
|
177
|
-
ROUND(AVG(
|
|
178
|
-
CASE WHEN wd.active_devs > 0
|
|
179
|
-
THEN (wa.devs_using::numeric / wd.active_devs) * 100
|
|
180
|
-
ELSE 0
|
|
181
|
-
END
|
|
182
|
-
)) as score,
|
|
183
|
-
COUNT(DISTINCT wa.standard_id) as standards_count,
|
|
184
|
-
MAX(wd.active_devs) as developer_count
|
|
185
|
-
FROM weekly_adoption wa
|
|
186
|
-
JOIN weekly_devs wd ON wa.week = wd.week
|
|
187
|
-
GROUP BY wa.week
|
|
188
|
-
ORDER BY wa.week
|
|
189
|
-
`, [userCompanyId, periodStart]);
|
|
190
|
-
|
|
191
|
-
convergenceTrend = trendResult.rows.map(row => ({
|
|
192
|
-
week: row.week,
|
|
193
|
-
score: parseInt(row.score) || 0,
|
|
194
|
-
standards_count: parseInt(row.standards_count),
|
|
195
|
-
developer_count: parseInt(row.developer_count)
|
|
196
|
-
}));
|
|
197
|
-
} catch (e) {
|
|
198
|
-
console.log('[Convergence] Trend query failed:', e.message);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Determine trend direction
|
|
202
|
-
let trend = 'stable';
|
|
203
|
-
if (convergenceTrend.length >= 2) {
|
|
204
|
-
const recent = convergenceTrend[convergenceTrend.length - 1].score;
|
|
205
|
-
const earlier = convergenceTrend[0].score;
|
|
206
|
-
if (recent > earlier + 5) trend = 'improving';
|
|
207
|
-
else if (recent < earlier - 5) trend = 'declining';
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return createSuccessResponse({
|
|
211
|
-
convergence_score: convergenceScore,
|
|
212
|
-
trend,
|
|
213
|
-
developer_count: activeDevelopers.length,
|
|
214
|
-
standards_in_use: byStandard.length,
|
|
215
|
-
by_standard: byStandard,
|
|
216
|
-
by_developer: byDeveloper,
|
|
217
|
-
convergence_trend: convergenceTrend
|
|
218
|
-
}, 'Convergence analytics retrieved');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
exports.handler = wrapHandler(getConvergence);
|
|
222
|
-
|
|
223
|
-
function parsePeriod(period) {
|
|
224
|
-
const match = period.match(/^(\d+)([dwm])$/);
|
|
225
|
-
if (!match) return 30;
|
|
226
|
-
|
|
227
|
-
const [, num, unit] = match;
|
|
228
|
-
const n = parseInt(num);
|
|
229
|
-
|
|
230
|
-
switch (unit) {
|
|
231
|
-
case 'd': return n;
|
|
232
|
-
case 'w': return n * 7;
|
|
233
|
-
case 'm': return n * 30;
|
|
234
|
-
default: return 30;
|
|
235
|
-
}
|
|
236
|
-
}
|