@equilateral_ai/mindmeld 3.3.1 → 3.5.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 +1 -10
- package/hooks/pre-compact.js +213 -25
- package/hooks/session-end.js +112 -3
- package/hooks/session-start.js +635 -41
- package/hooks/subagent-start.js +150 -0
- package/hooks/subagent-stop.js +184 -0
- package/package.json +8 -7
- package/scripts/init-project.js +74 -33
- package/scripts/mcp-bridge.js +220 -0
- package/src/core/CorrelationAnalyzer.js +157 -0
- package/src/core/LLMPatternDetector.js +198 -0
- package/src/core/RelevanceDetector.js +123 -36
- package/src/core/StandardsIngestion.js +119 -18
- package/src/handlers/activity/activityGetMe.js +1 -1
- package/src/handlers/activity/activityGetTeam.js +100 -55
- package/src/handlers/admin/adminSetup.js +216 -0
- package/src/handlers/alerts/alertsAcknowledge.js +6 -6
- package/src/handlers/alerts/alertsGet.js +11 -11
- package/src/handlers/analytics/activitySummaryGet.js +34 -35
- package/src/handlers/analytics/coachingGet.js +11 -11
- package/src/handlers/analytics/convergenceGet.js +236 -0
- package/src/handlers/analytics/developerScoreGet.js +41 -111
- package/src/handlers/collaborators/collaboratorInvite.js +1 -1
- package/src/handlers/company/companyUsersDelete.js +141 -0
- package/src/handlers/company/companyUsersGet.js +90 -0
- package/src/handlers/company/companyUsersPost.js +267 -0
- package/src/handlers/company/companyUsersPut.js +76 -0
- package/src/handlers/correlations/correlationsDeveloperGet.js +12 -12
- package/src/handlers/correlations/correlationsGet.js +8 -8
- package/src/handlers/correlations/correlationsProjectGet.js +5 -5
- package/src/handlers/enterprise/controlTowerGet.js +224 -0
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +48 -9
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +1 -3
- package/src/handlers/github/githubConnectionStatus.js +1 -1
- package/src/handlers/github/githubDiscoverPatterns.js +4 -2
- package/src/handlers/github/githubPatternsReview.js +7 -36
- package/src/handlers/health/healthGet.js +55 -0
- package/src/handlers/helpers/checkSuperAdmin.js +13 -14
- package/src/handlers/helpers/mindmeldMcpCore.js +594 -0
- package/src/handlers/helpers/subscriptionTiers.js +27 -27
- package/src/handlers/mcp/mcpHandler.js +569 -0
- package/src/handlers/mcp/mindmeldMcpHandler.js +124 -0
- package/src/handlers/mcp/mindmeldMcpStreamHandler.js +243 -0
- package/src/handlers/notifications/sendNotification.js +18 -18
- package/src/handlers/patterns/patternEvaluatePromotionPost.js +173 -0
- package/src/handlers/projects/projectCreate.js +124 -10
- package/src/handlers/projects/projectDelete.js +4 -4
- package/src/handlers/projects/projectGet.js +8 -8
- package/src/handlers/projects/projectUpdate.js +4 -4
- package/src/handlers/reports/aiLeverage.js +34 -30
- package/src/handlers/reports/engineeringInvestment.js +16 -16
- package/src/handlers/reports/riskForecast.js +41 -21
- package/src/handlers/reports/standardsRoi.js +101 -9
- package/src/handlers/scheduled/maturityUpdateJob.js +166 -0
- package/src/handlers/sessions/sessionStandardsPost.js +43 -7
- package/src/handlers/standards/discoveriesGet.js +93 -0
- package/src/handlers/standards/projectStandardsGet.js +2 -2
- package/src/handlers/standards/projectStandardsPut.js +2 -2
- package/src/handlers/standards/standardsRelevantPost.js +107 -12
- package/src/handlers/standards/standardsTransition.js +112 -15
- package/src/handlers/stripe/billingPortalPost.js +1 -1
- package/src/handlers/stripe/enterpriseCheckoutPost.js +2 -2
- package/src/handlers/stripe/subscriptionCreatePost.js +2 -2
- package/src/handlers/stripe/webhookPost.js +42 -14
- package/src/handlers/user/apiTokenCreate.js +71 -0
- package/src/handlers/user/apiTokenList.js +64 -0
- package/src/handlers/user/userSplashGet.js +90 -73
- package/src/handlers/users/cognitoPostConfirmation.js +37 -1
- package/src/handlers/users/cognitoPreSignUp.js +114 -0
- package/src/handlers/users/userGet.js +15 -11
- package/src/handlers/webhooks/githubWebhook.js +117 -125
- package/src/index.js +8 -5
|
@@ -20,10 +20,10 @@ async function getProjects({ queryStringParameters: queryParams = {}, requestCon
|
|
|
20
20
|
|
|
21
21
|
// Get user's company access
|
|
22
22
|
const entitlementQuery = `
|
|
23
|
-
SELECT ue.
|
|
24
|
-
FROM
|
|
25
|
-
JOIN
|
|
26
|
-
WHERE ue.
|
|
23
|
+
SELECT ue.company_id, ue.admin, c.company_name
|
|
24
|
+
FROM rapport.user_entitlements ue
|
|
25
|
+
JOIN rapport.companies c ON ue.company_id = c.company_id
|
|
26
|
+
WHERE ue.email_address = $1
|
|
27
27
|
`;
|
|
28
28
|
const entitlements = await executeQuery(entitlementQuery, [email]);
|
|
29
29
|
|
|
@@ -31,7 +31,7 @@ async function getProjects({ queryStringParameters: queryParams = {}, requestCon
|
|
|
31
31
|
return createErrorResponse(403, 'No company access');
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const companyIds = entitlements.rows.map(e => e.
|
|
34
|
+
const companyIds = entitlements.rows.map(e => e.company_id);
|
|
35
35
|
|
|
36
36
|
// Optional filter by specific company
|
|
37
37
|
let targetCompanyIds = companyIds;
|
|
@@ -53,7 +53,7 @@ async function getProjects({ queryStringParameters: queryParams = {}, requestCon
|
|
|
53
53
|
p.created_at,
|
|
54
54
|
p.last_active,
|
|
55
55
|
p.archived,
|
|
56
|
-
c.
|
|
56
|
+
c.company_name,
|
|
57
57
|
COUNT(DISTINCT pc.email_address) as collaborator_count,
|
|
58
58
|
COALESCE(
|
|
59
59
|
json_agg(
|
|
@@ -66,11 +66,11 @@ async function getProjects({ queryStringParameters: queryParams = {}, requestCon
|
|
|
66
66
|
'[]'
|
|
67
67
|
) as collaborators
|
|
68
68
|
FROM rapport.projects p
|
|
69
|
-
JOIN
|
|
69
|
+
JOIN rapport.companies c ON p.company_id = c.company_id
|
|
70
70
|
LEFT JOIN rapport.project_collaborators pc ON p.project_id = pc.project_id
|
|
71
71
|
WHERE p.company_id = ANY($1::varchar[])
|
|
72
72
|
AND p.archived = false
|
|
73
|
-
GROUP BY p.project_id, c.
|
|
73
|
+
GROUP BY p.project_id, c.company_name
|
|
74
74
|
ORDER BY p.last_active DESC NULLS LAST, p.created_at DESC
|
|
75
75
|
`;
|
|
76
76
|
|
|
@@ -30,14 +30,14 @@ async function updateProject({ body: requestBody = {}, requestContext }) {
|
|
|
30
30
|
p.project_id,
|
|
31
31
|
p.company_id,
|
|
32
32
|
pc.role,
|
|
33
|
-
ue.
|
|
33
|
+
ue.admin as company_admin
|
|
34
34
|
FROM rapport.projects p
|
|
35
35
|
LEFT JOIN rapport.project_collaborators pc
|
|
36
36
|
ON p.project_id = pc.project_id
|
|
37
37
|
AND pc.email_address = $1
|
|
38
|
-
LEFT JOIN
|
|
39
|
-
ON ue.
|
|
40
|
-
AND ue.
|
|
38
|
+
LEFT JOIN rapport.user_entitlements ue
|
|
39
|
+
ON ue.email_address = $1
|
|
40
|
+
AND ue.company_id = p.company_id
|
|
41
41
|
WHERE p.project_id = $2
|
|
42
42
|
`;
|
|
43
43
|
const accessCheck = await executeQuery(accessQuery, [email, projectId]);
|
|
@@ -20,13 +20,13 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
20
20
|
const period = params.period || '30d';
|
|
21
21
|
const companyId = params.Company_ID;
|
|
22
22
|
|
|
23
|
-
// Validate access - must be manager/admin
|
|
23
|
+
// Validate access - must be manager/admin
|
|
24
24
|
const accessCheck = await executeQuery(`
|
|
25
|
-
SELECT ue.
|
|
26
|
-
FROM
|
|
27
|
-
WHERE ue.
|
|
28
|
-
AND (ue.
|
|
29
|
-
${companyId ? 'AND ue.
|
|
25
|
+
SELECT ue.company_id
|
|
26
|
+
FROM rapport.user_entitlements ue
|
|
27
|
+
WHERE ue.email_address = $1
|
|
28
|
+
AND (ue.admin = true OR ue.manager = true)
|
|
29
|
+
${companyId ? 'AND ue.company_id = $2' : ''}
|
|
30
30
|
LIMIT 1
|
|
31
31
|
`, companyId ? [email, companyId] : [email]);
|
|
32
32
|
|
|
@@ -34,7 +34,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
34
34
|
return createErrorResponse(403, 'Manager or Admin access required');
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const userCompanyId = companyId || accessCheck.rows[0].
|
|
37
|
+
const userCompanyId = companyId || accessCheck.rows[0].company_id;
|
|
38
38
|
const periodDays = parsePeriod(period);
|
|
39
39
|
const periodStart = new Date();
|
|
40
40
|
periodStart.setDate(periodStart.getDate() - periodDays);
|
|
@@ -49,7 +49,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
49
49
|
AVG(s.duration_seconds / 60.0) as avg_session_minutes
|
|
50
50
|
FROM rapport.sessions s
|
|
51
51
|
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
52
|
-
WHERE p.
|
|
52
|
+
WHERE p.company_id = $1
|
|
53
53
|
AND s.started_at >= $2
|
|
54
54
|
`, [userCompanyId, periodStart]);
|
|
55
55
|
} catch (e) {
|
|
@@ -69,7 +69,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
69
69
|
AVG(dm.compliance_score) as avg_compliance
|
|
70
70
|
FROM rapport.developer_metrics dm
|
|
71
71
|
JOIN rapport.git_repositories r ON dm.repo_id = r.repo_id
|
|
72
|
-
WHERE r.
|
|
72
|
+
WHERE r.company_id = $1
|
|
73
73
|
AND dm.period_start >= $2
|
|
74
74
|
GROUP BY dm.developer_email, dm.developer_name
|
|
75
75
|
ORDER BY total_commits DESC
|
|
@@ -86,7 +86,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
86
86
|
SELECT DISTINCT s.email_address
|
|
87
87
|
FROM rapport.sessions s
|
|
88
88
|
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
89
|
-
WHERE p.
|
|
89
|
+
WHERE p.company_id = $1
|
|
90
90
|
AND s.started_at >= $2
|
|
91
91
|
`, [userCompanyId, periodStart]);
|
|
92
92
|
} catch (e) {
|
|
@@ -106,6 +106,9 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
const sessionData = sessionMetrics.rows[0] || {};
|
|
109
|
+
const multiplier = calculateMultiplier(aiAssistedDevs, nonAiDevs);
|
|
110
|
+
const totalCommits = devMetrics.rows.reduce((sum, d) => sum + parseInt(d.total_commits || 0), 0);
|
|
111
|
+
const aiCommits = aiAssistedDevs.reduce((sum, d) => sum + parseInt(d.total_commits || 0), 0);
|
|
109
112
|
|
|
110
113
|
return createSuccessResponse({
|
|
111
114
|
report_type: 'ai_leverage',
|
|
@@ -113,28 +116,29 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
113
116
|
period_start: periodStart.toISOString(),
|
|
114
117
|
period_end: new Date().toISOString(),
|
|
115
118
|
summary: {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
total_sessions: parseInt(sessionData.total_sessions) || 0,
|
|
120
|
+
unique_ai_users: aiAssistedDevs.length,
|
|
121
|
+
avg_session_minutes: parseFloat(sessionData.avg_session_minutes || 0).toFixed(1),
|
|
122
|
+
ai_assisted_commits: aiCommits,
|
|
123
|
+
total_commits: totalCommits,
|
|
124
|
+
ai_commit_percentage: totalCommits > 0 ? ((aiCommits / totalCommits) * 100).toFixed(1) : '0',
|
|
125
|
+
productivity_multiplier: multiplier.available ? multiplier.value : '1.0'
|
|
120
126
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
comparison: {
|
|
128
|
+
ai_assisted: {
|
|
129
|
+
developer_count: aiAssistedDevs.length,
|
|
130
|
+
avg_commits_per_dev: calcAvg(aiAssistedDevs, 'total_commits').toFixed(1),
|
|
131
|
+
avg_lines_per_dev: calcAvg(aiAssistedDevs, 'lines_added').toFixed(0),
|
|
132
|
+
avg_compliance: calcAvg(aiAssistedDevs, 'avg_compliance').toFixed(1)
|
|
133
|
+
},
|
|
134
|
+
non_ai: {
|
|
135
|
+
developer_count: nonAiDevs.length,
|
|
136
|
+
avg_commits_per_dev: calcAvg(nonAiDevs, 'total_commits').toFixed(1),
|
|
137
|
+
avg_lines_per_dev: calcAvg(nonAiDevs, 'lines_added').toFixed(0),
|
|
138
|
+
avg_compliance: calcAvg(nonAiDevs, 'avg_compliance').toFixed(1)
|
|
139
|
+
}
|
|
128
140
|
},
|
|
129
|
-
|
|
130
|
-
developers_count: nonAiDevs.length,
|
|
131
|
-
avg_commits: calcAvg(nonAiDevs, 'total_commits').toFixed(1),
|
|
132
|
-
avg_lines: calcAvg(nonAiDevs, 'lines_added').toFixed(0),
|
|
133
|
-
avg_prs: calcAvg(nonAiDevs, 'prs_merged').toFixed(1),
|
|
134
|
-
avg_compliance: calcAvg(nonAiDevs, 'avg_compliance').toFixed(1),
|
|
135
|
-
developers: nonAiDevs
|
|
136
|
-
},
|
|
137
|
-
productivity_multiplier: calculateMultiplier(aiAssistedDevs, nonAiDevs),
|
|
141
|
+
standards_effectiveness: [],
|
|
138
142
|
insights: generateInsights(aiAssistedDevs, nonAiDevs, sessionData)
|
|
139
143
|
});
|
|
140
144
|
});
|
|
@@ -20,13 +20,13 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
20
20
|
const period = params.period || '7d';
|
|
21
21
|
const companyId = params.Company_ID;
|
|
22
22
|
|
|
23
|
-
// Validate access - must be manager/admin
|
|
23
|
+
// Validate access - must be manager/admin
|
|
24
24
|
const accessCheck = await executeQuery(`
|
|
25
|
-
SELECT ue.
|
|
26
|
-
FROM
|
|
27
|
-
WHERE ue.
|
|
28
|
-
AND (ue.
|
|
29
|
-
${companyId ? 'AND ue.
|
|
25
|
+
SELECT ue.company_id
|
|
26
|
+
FROM rapport.user_entitlements ue
|
|
27
|
+
WHERE ue.email_address = $1
|
|
28
|
+
AND (ue.admin = true OR ue.manager = true)
|
|
29
|
+
${companyId ? 'AND ue.company_id = $2' : ''}
|
|
30
30
|
LIMIT 1
|
|
31
31
|
`, companyId ? [email, companyId] : [email]);
|
|
32
32
|
|
|
@@ -34,7 +34,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
34
34
|
return createErrorResponse(403, 'Manager or Admin access required');
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const userCompanyId = companyId || accessCheck.rows[0].
|
|
37
|
+
const userCompanyId = companyId || accessCheck.rows[0].company_id;
|
|
38
38
|
const periodDays = parsePeriod(period);
|
|
39
39
|
const periodStart = new Date();
|
|
40
40
|
periodStart.setDate(periodStart.getDate() - periodDays);
|
|
@@ -43,16 +43,16 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
43
43
|
const investment = await executeQuery(`
|
|
44
44
|
WITH commit_modules AS (
|
|
45
45
|
SELECT
|
|
46
|
-
|
|
46
|
+
c.repo_name as module,
|
|
47
47
|
COUNT(*) as commit_count,
|
|
48
48
|
SUM(c.lines_added) as lines_added,
|
|
49
49
|
SUM(c.lines_removed) as lines_removed,
|
|
50
50
|
COUNT(DISTINCT c.author_email) as contributors
|
|
51
51
|
FROM rapport.commits c
|
|
52
52
|
JOIN rapport.git_repositories r ON c.repo_id = r.repo_id
|
|
53
|
-
WHERE r.
|
|
54
|
-
AND c.
|
|
55
|
-
GROUP BY
|
|
53
|
+
WHERE r.company_id = $1
|
|
54
|
+
AND c.commit_timestamp >= $2
|
|
55
|
+
GROUP BY c.repo_name
|
|
56
56
|
)
|
|
57
57
|
SELECT
|
|
58
58
|
module,
|
|
@@ -60,8 +60,8 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
60
60
|
lines_added,
|
|
61
61
|
lines_removed,
|
|
62
62
|
contributors,
|
|
63
|
-
ROUND(commit_count * 100.0 / SUM(commit_count) OVER (), 1) as commit_percentage,
|
|
64
|
-
ROUND((lines_added + lines_removed) * 100.0 / SUM(lines_added + lines_removed) OVER (), 1) as churn_percentage
|
|
63
|
+
ROUND(commit_count * 100.0 / NULLIF(SUM(commit_count) OVER (), 0), 1) as commit_percentage,
|
|
64
|
+
ROUND((lines_added + lines_removed) * 100.0 / NULLIF(SUM(lines_added + lines_removed) OVER (), 0), 1) as churn_percentage
|
|
65
65
|
FROM commit_modules
|
|
66
66
|
ORDER BY commit_count DESC
|
|
67
67
|
LIMIT 20
|
|
@@ -78,7 +78,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
78
78
|
AVG(dm.compliance_score) as avg_compliance
|
|
79
79
|
FROM rapport.developer_metrics dm
|
|
80
80
|
JOIN rapport.git_repositories r ON dm.repo_id = r.repo_id
|
|
81
|
-
WHERE r.
|
|
81
|
+
WHERE r.company_id = $1
|
|
82
82
|
AND dm.period_start >= $2
|
|
83
83
|
GROUP BY dm.developer_name, dm.developer_email
|
|
84
84
|
ORDER BY total_commits DESC
|
|
@@ -93,8 +93,8 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
93
93
|
COUNT(DISTINCT c.author_email) as contributors,
|
|
94
94
|
SUM(c.lines_added) as lines_added
|
|
95
95
|
FROM rapport.git_repositories r
|
|
96
|
-
LEFT JOIN rapport.commits c ON r.repo_id = c.repo_id AND c.
|
|
97
|
-
WHERE r.
|
|
96
|
+
LEFT JOIN rapport.commits c ON r.repo_id = c.repo_id AND c.commit_timestamp >= $2
|
|
97
|
+
WHERE r.company_id = $1
|
|
98
98
|
GROUP BY r.repo_id, r.repo_name
|
|
99
99
|
ORDER BY commit_count DESC
|
|
100
100
|
`, [userCompanyId, periodStart]);
|
|
@@ -19,13 +19,13 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
19
19
|
const params = queryStringParameters || {};
|
|
20
20
|
const companyId = params.Company_ID;
|
|
21
21
|
|
|
22
|
-
// Validate access - must be manager/admin
|
|
22
|
+
// Validate access - must be manager/admin
|
|
23
23
|
const accessCheck = await executeQuery(`
|
|
24
|
-
SELECT ue.
|
|
25
|
-
FROM
|
|
26
|
-
WHERE ue.
|
|
27
|
-
AND (ue.
|
|
28
|
-
${companyId ? 'AND ue.
|
|
24
|
+
SELECT ue.company_id
|
|
25
|
+
FROM rapport.user_entitlements ue
|
|
26
|
+
WHERE ue.email_address = $1
|
|
27
|
+
AND (ue.admin = true OR ue.manager = true)
|
|
28
|
+
${companyId ? 'AND ue.company_id = $2' : ''}
|
|
29
29
|
LIMIT 1
|
|
30
30
|
`, companyId ? [email, companyId] : [email]);
|
|
31
31
|
|
|
@@ -33,7 +33,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
33
33
|
return createErrorResponse(403, 'Manager or Admin access required');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const userCompanyId = companyId || accessCheck.rows[0].
|
|
36
|
+
const userCompanyId = companyId || accessCheck.rows[0].company_id;
|
|
37
37
|
|
|
38
38
|
// Knowledge silos (bus factor analysis)
|
|
39
39
|
let silos = { rows: [] };
|
|
@@ -47,7 +47,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
47
47
|
ks.risk_level
|
|
48
48
|
FROM rapport.knowledge_silos ks
|
|
49
49
|
JOIN rapport.git_repositories r ON ks.repo_id = r.repo_id
|
|
50
|
-
WHERE r.
|
|
50
|
+
WHERE r.company_id = $1
|
|
51
51
|
ORDER BY
|
|
52
52
|
CASE ks.risk_level
|
|
53
53
|
WHEN 'critical' THEN 1
|
|
@@ -74,7 +74,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
74
74
|
pr.lines_added + pr.lines_removed as total_changes
|
|
75
75
|
FROM rapport.pull_requests pr
|
|
76
76
|
JOIN rapport.git_repositories r ON pr.repo_id = r.repo_id
|
|
77
|
-
WHERE r.
|
|
77
|
+
WHERE r.company_id = $1
|
|
78
78
|
AND pr.status = 'open'
|
|
79
79
|
AND pr.created_at < NOW() - INTERVAL '7 days'
|
|
80
80
|
ORDER BY pr.created_at ASC
|
|
@@ -95,7 +95,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
95
95
|
wp.peak_hour
|
|
96
96
|
FROM rapport.working_patterns wp
|
|
97
97
|
JOIN rapport.git_repositories r ON wp.repo_id = r.repo_id
|
|
98
|
-
WHERE r.
|
|
98
|
+
WHERE r.company_id = $1
|
|
99
99
|
AND wp.period_start >= NOW() - INTERVAL '30 days'
|
|
100
100
|
GROUP BY wp.developer_email, wp.peak_hour
|
|
101
101
|
HAVING SUM(wp.after_hours_commits) > 10 OR SUM(wp.weekend_commits) > 5
|
|
@@ -114,21 +114,41 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
114
114
|
|
|
115
115
|
const riskScore = Math.min(100, criticalSilos * 20 + highSilos * 10 + staleCount * 5 + burnoutCount * 5);
|
|
116
116
|
|
|
117
|
+
const riskLevel = riskScore >= 70 ? 'critical' : riskScore >= 40 ? 'elevated' : 'normal';
|
|
118
|
+
|
|
117
119
|
return createSuccessResponse({
|
|
118
120
|
report_type: 'risk_forecast',
|
|
119
121
|
generated_at: new Date().toISOString(),
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
overall_risk_score: riskScore,
|
|
123
|
+
risk_level: riskLevel,
|
|
124
|
+
summary: {
|
|
125
|
+
inactive_developers: 0,
|
|
126
|
+
high_velocity_devs: burnoutCount,
|
|
127
|
+
critical_silos: criticalSilos,
|
|
128
|
+
stale_prs: staleCount,
|
|
129
|
+
active_alerts: 0,
|
|
130
|
+
high_churn_files: 0
|
|
131
|
+
},
|
|
132
|
+
risks: {
|
|
133
|
+
knowledge_silos: silos.rows,
|
|
134
|
+
inactive_developers: [],
|
|
135
|
+
burnout_risk: burnoutRisk.rows.map(b => ({
|
|
136
|
+
email: b.developer_email,
|
|
137
|
+
name: b.developer_email,
|
|
138
|
+
commits_7d: 0,
|
|
139
|
+
weekend_commits: parseInt(b.weekend_commits) || 0,
|
|
140
|
+
after_hours: parseInt(b.after_hours_commits) || 0
|
|
141
|
+
})),
|
|
142
|
+
review_bottleneck: stalePRs.rows.map(pr => ({
|
|
143
|
+
repo: '',
|
|
144
|
+
pr_number: 0,
|
|
145
|
+
title: pr.title,
|
|
146
|
+
author: pr.author_email,
|
|
147
|
+
days_open: parseInt(pr.days_open) || 0
|
|
148
|
+
})),
|
|
149
|
+
high_churn_files: []
|
|
128
150
|
},
|
|
129
|
-
|
|
130
|
-
stale_pull_requests: stalePRs.rows,
|
|
131
|
-
burnout_indicators: burnoutRisk.rows,
|
|
151
|
+
active_alerts: [],
|
|
132
152
|
recommendations: generateRecommendations(silos.rows, stalePRs.rows, burnoutRisk.rows)
|
|
133
153
|
});
|
|
134
154
|
});
|
|
@@ -20,13 +20,13 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
20
20
|
const period = params.period || '30d';
|
|
21
21
|
const companyId = params.Company_ID;
|
|
22
22
|
|
|
23
|
-
// Validate access - must be manager/admin
|
|
23
|
+
// Validate access - must be manager/admin
|
|
24
24
|
const accessCheck = await executeQuery(`
|
|
25
|
-
SELECT ue.
|
|
26
|
-
FROM
|
|
27
|
-
WHERE ue.
|
|
28
|
-
AND (ue.
|
|
29
|
-
${companyId ? 'AND ue.
|
|
25
|
+
SELECT ue.company_id
|
|
26
|
+
FROM rapport.user_entitlements ue
|
|
27
|
+
WHERE ue.email_address = $1
|
|
28
|
+
AND (ue.admin = true OR ue.manager = true)
|
|
29
|
+
${companyId ? 'AND ue.company_id = $2' : ''}
|
|
30
30
|
LIMIT 1
|
|
31
31
|
`, companyId ? [email, companyId] : [email]);
|
|
32
32
|
|
|
@@ -34,7 +34,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
34
34
|
return createErrorResponse(403, 'Manager or Admin access required');
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const userCompanyId = companyId || accessCheck.rows[0].
|
|
37
|
+
const userCompanyId = companyId || accessCheck.rows[0].company_id;
|
|
38
38
|
const periodDays = parsePeriod(period);
|
|
39
39
|
const periodStart = new Date();
|
|
40
40
|
periodStart.setDate(periodStart.getDate() - periodDays);
|
|
@@ -52,7 +52,7 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
52
52
|
SUM(dm.anti_pattern_commits) as anti_pattern_commits
|
|
53
53
|
FROM rapport.developer_metrics dm
|
|
54
54
|
JOIN rapport.git_repositories r ON dm.repo_id = r.repo_id
|
|
55
|
-
WHERE r.
|
|
55
|
+
WHERE r.company_id = $1
|
|
56
56
|
AND dm.period_start >= $2
|
|
57
57
|
GROUP BY dm.developer_email, dm.developer_name
|
|
58
58
|
HAVING SUM(dm.commit_count) > 0
|
|
@@ -91,6 +91,95 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
91
91
|
const compliantCommits = complianceData.reduce((sum, r) => sum + parseInt(r.compliant_commits || 0), 0);
|
|
92
92
|
const antiPatternCommits = complianceData.reduce((sum, r) => sum + parseInt(r.anti_pattern_commits || 0), 0);
|
|
93
93
|
|
|
94
|
+
// Get standards shown count from session_standards
|
|
95
|
+
let standardsShown = 0;
|
|
96
|
+
try {
|
|
97
|
+
const shownResult = await executeQuery(`
|
|
98
|
+
SELECT COUNT(*) as total
|
|
99
|
+
FROM rapport.session_standards ss
|
|
100
|
+
JOIN rapport.sessions s ON ss.session_id = s.session_id
|
|
101
|
+
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
102
|
+
WHERE p.company_id = $1
|
|
103
|
+
AND s.started_at >= $2
|
|
104
|
+
`, [userCompanyId, periodStart]);
|
|
105
|
+
standardsShown = parseInt(shownResult.rows[0]?.total) || 0;
|
|
106
|
+
} catch (e) {
|
|
107
|
+
// Table might not have data
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Adoption trend — standards injected per day
|
|
111
|
+
let adoptionTrend = [];
|
|
112
|
+
try {
|
|
113
|
+
const trendResult = await executeQuery(`
|
|
114
|
+
SELECT
|
|
115
|
+
DATE(ss.created_at) as date,
|
|
116
|
+
COUNT(*) as standards_shown,
|
|
117
|
+
COUNT(DISTINCT ss.session_id) as sessions,
|
|
118
|
+
COUNT(CASE WHEN ss.followed = true THEN 1 END) as followed,
|
|
119
|
+
COUNT(CASE WHEN ss.violated = true THEN 1 END) as violated
|
|
120
|
+
FROM rapport.session_standards ss
|
|
121
|
+
JOIN rapport.sessions s ON ss.session_id = s.session_id
|
|
122
|
+
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
123
|
+
WHERE p.company_id = $1
|
|
124
|
+
AND ss.created_at >= $2
|
|
125
|
+
GROUP BY DATE(ss.created_at)
|
|
126
|
+
ORDER BY date
|
|
127
|
+
`, [userCompanyId, periodStart]);
|
|
128
|
+
adoptionTrend = trendResult.rows;
|
|
129
|
+
} catch (e) {
|
|
130
|
+
// Table might not have data
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Top standards — most frequently injected
|
|
134
|
+
let topStandards = [];
|
|
135
|
+
try {
|
|
136
|
+
const topResult = await executeQuery(`
|
|
137
|
+
SELECT
|
|
138
|
+
ss.standard_id,
|
|
139
|
+
ss.standard_name,
|
|
140
|
+
COUNT(*) as times_shown,
|
|
141
|
+
COUNT(DISTINCT ss.session_id) as unique_sessions,
|
|
142
|
+
ROUND(AVG(ss.relevance_score), 2) as avg_relevance,
|
|
143
|
+
COUNT(CASE WHEN ss.followed = true THEN 1 END) as times_followed,
|
|
144
|
+
COUNT(CASE WHEN ss.violated = true THEN 1 END) as times_violated
|
|
145
|
+
FROM rapport.session_standards ss
|
|
146
|
+
JOIN rapport.sessions s ON ss.session_id = s.session_id
|
|
147
|
+
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
148
|
+
WHERE p.company_id = $1
|
|
149
|
+
AND ss.created_at >= $2
|
|
150
|
+
GROUP BY ss.standard_id, ss.standard_name
|
|
151
|
+
ORDER BY times_shown DESC
|
|
152
|
+
LIMIT 10
|
|
153
|
+
`, [userCompanyId, periodStart]);
|
|
154
|
+
topStandards = topResult.rows;
|
|
155
|
+
} catch (e) {
|
|
156
|
+
// Table might not have data
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Anti-patterns detected — standards that were violated
|
|
160
|
+
let antiPatternsDetected = [];
|
|
161
|
+
try {
|
|
162
|
+
const violationResult = await executeQuery(`
|
|
163
|
+
SELECT
|
|
164
|
+
ss.standard_id,
|
|
165
|
+
ss.standard_name,
|
|
166
|
+
COUNT(*) as violation_count,
|
|
167
|
+
MAX(ss.created_at) as last_violated
|
|
168
|
+
FROM rapport.session_standards ss
|
|
169
|
+
JOIN rapport.sessions s ON ss.session_id = s.session_id
|
|
170
|
+
JOIN rapport.projects p ON s.project_id = p.project_id
|
|
171
|
+
WHERE p.company_id = $1
|
|
172
|
+
AND ss.created_at >= $2
|
|
173
|
+
AND ss.violated = true
|
|
174
|
+
GROUP BY ss.standard_id, ss.standard_name
|
|
175
|
+
ORDER BY violation_count DESC
|
|
176
|
+
LIMIT 10
|
|
177
|
+
`, [userCompanyId, periodStart]);
|
|
178
|
+
antiPatternsDetected = violationResult.rows;
|
|
179
|
+
} catch (e) {
|
|
180
|
+
// Table might not have data
|
|
181
|
+
}
|
|
182
|
+
|
|
94
183
|
return createSuccessResponse({
|
|
95
184
|
report_type: 'standards_roi',
|
|
96
185
|
period: period,
|
|
@@ -103,9 +192,12 @@ exports.handler = wrapHandler(async ({ requestContext, queryStringParameters })
|
|
|
103
192
|
compliance_rate: totalCommits > 0 ? ((compliantCommits / totalCommits) * 100).toFixed(1) : '0',
|
|
104
193
|
anti_pattern_commits: antiPatternCommits,
|
|
105
194
|
anti_pattern_rate: totalCommits > 0 ? ((antiPatternCommits / totalCommits) * 100).toFixed(1) : '0',
|
|
106
|
-
|
|
195
|
+
standards_shown: standardsShown
|
|
107
196
|
},
|
|
197
|
+
adoption_trend: adoptionTrend,
|
|
108
198
|
compliance_by_developer: complianceData,
|
|
199
|
+
top_standards: topStandards,
|
|
200
|
+
anti_patterns_detected: antiPatternsDetected,
|
|
109
201
|
configured_patterns: patterns.rows.filter(p => p.pattern_type === 'standard'),
|
|
110
202
|
anti_patterns: patterns.rows.filter(p => p.pattern_type === 'anti_pattern'),
|
|
111
203
|
insights: generateInsights(complianceData)
|