@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.
Files changed (72) hide show
  1. package/README.md +1 -10
  2. package/hooks/pre-compact.js +213 -25
  3. package/hooks/session-end.js +112 -3
  4. package/hooks/session-start.js +635 -41
  5. package/hooks/subagent-start.js +150 -0
  6. package/hooks/subagent-stop.js +184 -0
  7. package/package.json +8 -7
  8. package/scripts/init-project.js +74 -33
  9. package/scripts/mcp-bridge.js +220 -0
  10. package/src/core/CorrelationAnalyzer.js +157 -0
  11. package/src/core/LLMPatternDetector.js +198 -0
  12. package/src/core/RelevanceDetector.js +123 -36
  13. package/src/core/StandardsIngestion.js +119 -18
  14. package/src/handlers/activity/activityGetMe.js +1 -1
  15. package/src/handlers/activity/activityGetTeam.js +100 -55
  16. package/src/handlers/admin/adminSetup.js +216 -0
  17. package/src/handlers/alerts/alertsAcknowledge.js +6 -6
  18. package/src/handlers/alerts/alertsGet.js +11 -11
  19. package/src/handlers/analytics/activitySummaryGet.js +34 -35
  20. package/src/handlers/analytics/coachingGet.js +11 -11
  21. package/src/handlers/analytics/convergenceGet.js +236 -0
  22. package/src/handlers/analytics/developerScoreGet.js +41 -111
  23. package/src/handlers/collaborators/collaboratorInvite.js +1 -1
  24. package/src/handlers/company/companyUsersDelete.js +141 -0
  25. package/src/handlers/company/companyUsersGet.js +90 -0
  26. package/src/handlers/company/companyUsersPost.js +267 -0
  27. package/src/handlers/company/companyUsersPut.js +76 -0
  28. package/src/handlers/correlations/correlationsDeveloperGet.js +12 -12
  29. package/src/handlers/correlations/correlationsGet.js +8 -8
  30. package/src/handlers/correlations/correlationsProjectGet.js +5 -5
  31. package/src/handlers/enterprise/controlTowerGet.js +224 -0
  32. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +48 -9
  33. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +1 -3
  34. package/src/handlers/github/githubConnectionStatus.js +1 -1
  35. package/src/handlers/github/githubDiscoverPatterns.js +4 -2
  36. package/src/handlers/github/githubPatternsReview.js +7 -36
  37. package/src/handlers/health/healthGet.js +55 -0
  38. package/src/handlers/helpers/checkSuperAdmin.js +13 -14
  39. package/src/handlers/helpers/mindmeldMcpCore.js +594 -0
  40. package/src/handlers/helpers/subscriptionTiers.js +27 -27
  41. package/src/handlers/mcp/mcpHandler.js +569 -0
  42. package/src/handlers/mcp/mindmeldMcpHandler.js +124 -0
  43. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +243 -0
  44. package/src/handlers/notifications/sendNotification.js +18 -18
  45. package/src/handlers/patterns/patternEvaluatePromotionPost.js +173 -0
  46. package/src/handlers/projects/projectCreate.js +124 -10
  47. package/src/handlers/projects/projectDelete.js +4 -4
  48. package/src/handlers/projects/projectGet.js +8 -8
  49. package/src/handlers/projects/projectUpdate.js +4 -4
  50. package/src/handlers/reports/aiLeverage.js +34 -30
  51. package/src/handlers/reports/engineeringInvestment.js +16 -16
  52. package/src/handlers/reports/riskForecast.js +41 -21
  53. package/src/handlers/reports/standardsRoi.js +101 -9
  54. package/src/handlers/scheduled/maturityUpdateJob.js +166 -0
  55. package/src/handlers/sessions/sessionStandardsPost.js +43 -7
  56. package/src/handlers/standards/discoveriesGet.js +93 -0
  57. package/src/handlers/standards/projectStandardsGet.js +2 -2
  58. package/src/handlers/standards/projectStandardsPut.js +2 -2
  59. package/src/handlers/standards/standardsRelevantPost.js +107 -12
  60. package/src/handlers/standards/standardsTransition.js +112 -15
  61. package/src/handlers/stripe/billingPortalPost.js +1 -1
  62. package/src/handlers/stripe/enterpriseCheckoutPost.js +2 -2
  63. package/src/handlers/stripe/subscriptionCreatePost.js +2 -2
  64. package/src/handlers/stripe/webhookPost.js +42 -14
  65. package/src/handlers/user/apiTokenCreate.js +71 -0
  66. package/src/handlers/user/apiTokenList.js +64 -0
  67. package/src/handlers/user/userSplashGet.js +90 -73
  68. package/src/handlers/users/cognitoPostConfirmation.js +37 -1
  69. package/src/handlers/users/cognitoPreSignUp.js +114 -0
  70. package/src/handlers/users/userGet.js +15 -11
  71. package/src/handlers/webhooks/githubWebhook.js +117 -125
  72. 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."Company_ID", ue."Admin", c."Company_Name"
24
- FROM "UserEntitlements" ue
25
- JOIN "Company" c ON ue."Company_ID" = c."Company_ID"
26
- WHERE ue."Email_Address" = $1
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.Company_ID);
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."Company_Name",
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 "Company" c ON p.company_id = c."Company_ID"
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."Company_Name"
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."Admin" as company_admin
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 "UserEntitlements" ue
39
- ON ue."Email_Address" = $1
40
- AND ue."Company_ID" = p.company_id
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 (boolean columns in Tim-Combo schema)
23
+ // Validate access - must be manager/admin
24
24
  const accessCheck = await executeQuery(`
25
- SELECT ue."Company_ID"
26
- FROM "UserEntitlements" ue
27
- WHERE ue."Email_Address" = $1
28
- AND (ue."Admin" = true OR ue."Manager" = true)
29
- ${companyId ? 'AND ue."Company_ID" = $2' : ''}
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].Company_ID;
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."Company_ID" = $1
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."Company_ID" = $1
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."Company_ID" = $1
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
- ai_sessions: parseInt(sessionData.total_sessions) || 0,
117
- ai_users: aiAssistedDevs.length,
118
- non_ai_users: nonAiDevs.length,
119
- avg_session_minutes: parseFloat(sessionData.avg_session_minutes || 0).toFixed(1)
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
- ai_assisted: {
122
- developers_count: aiAssistedDevs.length,
123
- avg_commits: calcAvg(aiAssistedDevs, 'total_commits').toFixed(1),
124
- avg_lines: calcAvg(aiAssistedDevs, 'lines_added').toFixed(0),
125
- avg_prs: calcAvg(aiAssistedDevs, 'prs_merged').toFixed(1),
126
- avg_compliance: calcAvg(aiAssistedDevs, 'avg_compliance').toFixed(1),
127
- developers: aiAssistedDevs
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
- non_ai: {
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 (boolean columns in Tim-Combo schema)
23
+ // Validate access - must be manager/admin
24
24
  const accessCheck = await executeQuery(`
25
- SELECT ue."Company_ID"
26
- FROM "UserEntitlements" ue
27
- WHERE ue."Email_Address" = $1
28
- AND (ue."Admin" = true OR ue."Manager" = true)
29
- ${companyId ? 'AND ue."Company_ID" = $2' : ''}
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].Company_ID;
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
- COALESCE(SUBSTRING(c.file_path FROM '^([^/]+)'), 'root') as module,
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.Company_ID = $1
54
- AND c.committed_at >= $2
55
- GROUP BY module
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.Company_ID = $1
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.committed_at >= $2
97
- WHERE r.Company_ID = $1
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 (boolean columns in Tim-Combo schema)
22
+ // Validate access - must be manager/admin
23
23
  const accessCheck = await executeQuery(`
24
- SELECT ue."Company_ID"
25
- FROM "UserEntitlements" ue
26
- WHERE ue."Email_Address" = $1
27
- AND (ue."Admin" = true OR ue."Manager" = true)
28
- ${companyId ? 'AND ue."Company_ID" = $2' : ''}
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].Company_ID;
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."Company_ID" = $1
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."Company_ID" = $1
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."Company_ID" = $1
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
- risk_score: {
121
- value: riskScore,
122
- level: riskScore >= 70 ? 'critical' : riskScore >= 40 ? 'elevated' : 'normal',
123
- factors: {
124
- knowledge_silos: criticalSilos + highSilos,
125
- stale_prs: staleCount,
126
- burnout_indicators: burnoutCount
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
- knowledge_silos: silos.rows,
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 (boolean columns in Tim-Combo schema)
23
+ // Validate access - must be manager/admin
24
24
  const accessCheck = await executeQuery(`
25
- SELECT ue."Company_ID"
26
- FROM "UserEntitlements" ue
27
- WHERE ue."Email_Address" = $1
28
- AND (ue."Admin" = true OR ue."Manager" = true)
29
- ${companyId ? 'AND ue."Company_ID" = $2' : ''}
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].Company_ID;
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."Company_ID" = $1
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
- patterns_configured: patterns.rows.length
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)