@equilateral_ai/mindmeld 3.5.2 → 4.0.1

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 (140) hide show
  1. package/hooks/session-end.js +25 -0
  2. package/hooks/session-start.js +363 -83
  3. package/hooks/session-watcher.js +585 -0
  4. package/package.json +19 -13
  5. package/scripts/init-project.js +9 -23
  6. package/src/client/dbShim.js +16 -0
  7. package/src/core/AuthManager.js +3 -2
  8. package/src/handlers/helpers/dbOperations.js +9 -46
  9. package/src/index.js +2 -217
  10. package/src/utils/piiMask.js +16 -0
  11. package/scripts/harvest.js +0 -601
  12. package/scripts/inject.js +0 -409
  13. package/scripts/mcp-bridge.js +0 -220
  14. package/scripts/repo-analyzer.js +0 -870
  15. package/src/collaboration/CollaborationPrompt.js +0 -460
  16. package/src/core/AlertEngine.js +0 -813
  17. package/src/core/AlertNotifier.js +0 -363
  18. package/src/core/CorrelationAnalyzer.js +0 -931
  19. package/src/core/CrossReferenceEngine.js +0 -624
  20. package/src/core/CurationEngine.js +0 -688
  21. package/src/core/DeprecationScheduler.js +0 -183
  22. package/src/core/LoadBearingDetector.js +0 -242
  23. package/src/core/NotificationService.js +0 -1032
  24. package/src/core/RapportOrchestrator.js +0 -632
  25. package/src/core/RelevanceDetector.js +0 -694
  26. package/src/core/StandardLifecycle.js +0 -244
  27. package/src/core/StandardsIngestion.js +0 -991
  28. package/src/core/TeamLoadBearingDetector.js +0 -431
  29. package/src/core/parsers/adrParser.js +0 -479
  30. package/src/core/parsers/cursorRulesParser.js +0 -564
  31. package/src/core/parsers/eslintParser.js +0 -439
  32. package/src/database/dbOperations.js +0 -105
  33. package/src/handlers/activity/activityGetMe.js +0 -98
  34. package/src/handlers/activity/activityGetTeam.js +0 -175
  35. package/src/handlers/admin/adminSetup.js +0 -216
  36. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  37. package/src/handlers/alerts/alertsGet.js +0 -250
  38. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  39. package/src/handlers/analytics/coachingGet.js +0 -361
  40. package/src/handlers/analytics/convergenceGet.js +0 -236
  41. package/src/handlers/analytics/developerScoreGet.js +0 -137
  42. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  43. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  44. package/src/handlers/collaborators/collaboratorList.js +0 -82
  45. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  46. package/src/handlers/collaborators/inviteAccept.js +0 -122
  47. package/src/handlers/company/companyUsersDelete.js +0 -141
  48. package/src/handlers/company/companyUsersGet.js +0 -90
  49. package/src/handlers/company/companyUsersPost.js +0 -267
  50. package/src/handlers/company/companyUsersPut.js +0 -76
  51. package/src/handlers/context/contextGet.js +0 -57
  52. package/src/handlers/context/invariantsGet.js +0 -74
  53. package/src/handlers/context/loopsGet.js +0 -82
  54. package/src/handlers/context/notesCreate.js +0 -74
  55. package/src/handlers/context/purposeGet.js +0 -78
  56. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  57. package/src/handlers/correlations/correlationsGet.js +0 -93
  58. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  59. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  60. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  61. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  62. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  63. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  64. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  65. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  66. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  67. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  68. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  69. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  70. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  71. package/src/handlers/github/githubConnectionStatus.js +0 -49
  72. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  73. package/src/handlers/github/githubOAuthCallback.js +0 -178
  74. package/src/handlers/github/githubOAuthStart.js +0 -59
  75. package/src/handlers/github/githubPatternsReview.js +0 -76
  76. package/src/handlers/github/githubReposList.js +0 -105
  77. package/src/handlers/health/healthGet.js +0 -55
  78. package/src/handlers/helpers/auditLogger.js +0 -201
  79. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  80. package/src/handlers/helpers/decisionFrames.js +0 -29
  81. package/src/handlers/helpers/errorHandler.js +0 -49
  82. package/src/handlers/helpers/index.js +0 -138
  83. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  84. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  85. package/src/handlers/helpers/predictiveCache.js +0 -51
  86. package/src/handlers/helpers/projectAccess.js +0 -88
  87. package/src/handlers/helpers/responseUtil.js +0 -55
  88. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  89. package/src/handlers/mcp/mcpHandler.js +0 -569
  90. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  91. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  92. package/src/handlers/notifications/getPreferences.js +0 -84
  93. package/src/handlers/notifications/sendNotification.js +0 -170
  94. package/src/handlers/notifications/updatePreferences.js +0 -316
  95. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  96. package/src/handlers/patterns/patternUsagePost.js +0 -182
  97. package/src/handlers/patterns/patternViolationPost.js +0 -185
  98. package/src/handlers/projects/projectCreate.js +0 -248
  99. package/src/handlers/projects/projectDelete.js +0 -82
  100. package/src/handlers/projects/projectGet.js +0 -95
  101. package/src/handlers/projects/projectUpdate.js +0 -117
  102. package/src/handlers/reports/aiLeverage.js +0 -210
  103. package/src/handlers/reports/engineeringInvestment.js +0 -132
  104. package/src/handlers/reports/riskForecast.js +0 -206
  105. package/src/handlers/reports/standardsRoi.js +0 -254
  106. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  107. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  108. package/src/handlers/scheduled/generateAlerts.js +0 -135
  109. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  110. package/src/handlers/scheduled/refreshActivity.js +0 -21
  111. package/src/handlers/scheduled/scanCompliance.js +0 -334
  112. package/src/handlers/sessions/sessionEndPost.js +0 -180
  113. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  114. package/src/handlers/standards/catalogGet.js +0 -185
  115. package/src/handlers/standards/catalogSync.js +0 -120
  116. package/src/handlers/standards/discoveriesGet.js +0 -89
  117. package/src/handlers/standards/projectStandardsGet.js +0 -129
  118. package/src/handlers/standards/projectStandardsPut.js +0 -151
  119. package/src/handlers/standards/standardsAuditGet.js +0 -65
  120. package/src/handlers/standards/standardsParseUpload.js +0 -149
  121. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  122. package/src/handlers/standards/standardsTransition.js +0 -161
  123. package/src/handlers/stripe/addonManagePost.js +0 -240
  124. package/src/handlers/stripe/billingPortalPost.js +0 -93
  125. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  126. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  127. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  128. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  129. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  130. package/src/handlers/stripe/webhookPost.js +0 -482
  131. package/src/handlers/user/apiTokenCreate.js +0 -71
  132. package/src/handlers/user/apiTokenList.js +0 -64
  133. package/src/handlers/user/userSplashAck.js +0 -91
  134. package/src/handlers/user/userSplashGet.js +0 -211
  135. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  136. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  137. package/src/handlers/users/userEntitlementsGet.js +0 -89
  138. package/src/handlers/users/userGet.js +0 -118
  139. package/src/handlers/users/userProfilePut.js +0 -77
  140. package/src/handlers/webhooks/githubWebhook.js +0 -215
@@ -1,621 +0,0 @@
1
- /**
2
- * GitHub Discover Patterns Handler
3
- * Analyzes a GitHub repo to discover coding patterns
4
- *
5
- * POST /api/github/discover (CognitoAuthorizer)
6
- * Body: { project_id, owner, repo, branch }
7
- * Timeout: 120s, Memory: 512MB
8
- *
9
- * Enhanced features:
10
- * - Fetches and analyzes actual code content (not just file paths)
11
- * - Uses LLM analysis to identify patterns in YAML standards format
12
- * - Maps tech stack to relevant YAML standards categories
13
- */
14
-
15
- const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, verifyProjectAccess } = require('./helpers');
16
- const { LLMPatternDetector } = require('./core/LLMPatternDetector');
17
- const crypto = require('crypto');
18
- const https = require('https');
19
-
20
- // File patterns to sample for deep analysis
21
- const KEY_FILES_TO_SAMPLE = [
22
- // Backend patterns
23
- { pattern: /^src\/handlers\/[^/]+\.js$/, purpose: 'lambda_handlers', max: 3 },
24
- { pattern: /^src\/.*\/(helpers|utils|lib)\/[^/]+\.js$/, purpose: 'utilities', max: 2 },
25
- // Frontend patterns
26
- { pattern: /^src\/components\/[^/]+\.(jsx?|tsx?)$/, purpose: 'components', max: 3 },
27
- { pattern: /^src\/(pages|views)\/[^/]+\.(jsx?|tsx?)$/, purpose: 'pages', max: 2 },
28
- // Config patterns
29
- { pattern: /^(template|serverless|sam)\.ya?ml$/, purpose: 'infrastructure', max: 1 },
30
- // Core app files
31
- { pattern: /^src\/(app|index|main)\.(js|ts|jsx|tsx)$/, purpose: 'entry_point', max: 1 },
32
- ];
33
-
34
- // Tech stack to YAML standards categories mapping
35
- const TECH_TO_STANDARDS = {
36
- // Languages
37
- 'JavaScript': ['serverless-saas-aws', 'backend'],
38
- 'TypeScript': ['serverless-saas-aws', 'frontend-development'],
39
- 'Python': ['compliance-security'],
40
- 'Go': ['backend'],
41
- 'Rust': ['backend'],
42
-
43
- // Frameworks
44
- 'React': ['frontend-development'],
45
- 'Next.js': ['frontend-development'],
46
- 'Vue.js': ['frontend-development'],
47
- 'Angular': ['frontend-development'],
48
- 'Express': ['serverless-saas-aws', 'backend'],
49
- 'Fastify': ['serverless-saas-aws', 'backend'],
50
- 'Svelte': ['frontend-development'],
51
- 'Tailwind CSS': ['frontend-development'],
52
-
53
- // AWS Services
54
- 'AWS Lambda': ['serverless-saas-aws', 'backend'],
55
- 'Lambda': ['serverless-saas-aws', 'backend'],
56
- 'API Gateway': ['serverless-saas-aws'],
57
- 'Cognito': ['compliance-security', 'serverless-saas-aws'],
58
- 'DynamoDB': ['serverless-saas-aws'],
59
- 'S3': ['serverless-saas-aws'],
60
- 'CloudFormation': ['serverless-saas-aws', 'deployment'],
61
- 'SAM': ['serverless-saas-aws', 'deployment'],
62
-
63
- // Databases
64
- 'PostgreSQL': ['database', 'real-time-systems'],
65
- 'MySQL': ['database'],
66
- 'MongoDB': ['database'],
67
- 'Redis': ['real-time-systems'],
68
-
69
- // Patterns & Tools
70
- 'WebSockets': ['real-time-systems'],
71
- 'Docker': ['deployment'],
72
- 'GitHub Actions': ['deployment'],
73
- 'Jest': ['frontend-development'],
74
- 'Mocha': ['backend'],
75
- 'pytest': ['compliance-security'],
76
-
77
- // AI/ML
78
- 'Claude': ['multi-agent-orchestration'],
79
- 'OpenAI': ['multi-agent-orchestration'],
80
- 'LangChain': ['multi-agent-orchestration'],
81
- 'Bedrock': ['multi-agent-orchestration', 'serverless-saas-aws'],
82
- };
83
-
84
- function httpsGet(path, token) {
85
- return new Promise((resolve, reject) => {
86
- const req = https.request({
87
- hostname: 'api.github.com',
88
- path: path,
89
- method: 'GET',
90
- headers: {
91
- 'Authorization': `Bearer ${token}`,
92
- 'User-Agent': 'MindMeld-App',
93
- 'Accept': 'application/json'
94
- }
95
- }, (res) => {
96
- let data = '';
97
- res.on('data', chunk => data += chunk);
98
- res.on('end', () => {
99
- try {
100
- resolve({ statusCode: res.statusCode, body: JSON.parse(data) });
101
- } catch (e) {
102
- resolve({ statusCode: res.statusCode, body: data });
103
- }
104
- });
105
- });
106
- req.on('error', reject);
107
- req.end();
108
- });
109
- }
110
-
111
- function decryptToken(encryptedData, key) {
112
- const [ivHex, encrypted] = encryptedData.split(':');
113
- const iv = Buffer.from(ivHex, 'hex');
114
- const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv);
115
- let decrypted = decipher.update(encrypted, 'hex', 'utf8');
116
- decrypted += decipher.final('utf8');
117
- return decrypted;
118
- }
119
-
120
- /**
121
- * Select and fetch representative code files for deep analysis
122
- */
123
- async function fetchSampleFiles(tree, accessToken, owner, repo) {
124
- const files = tree.filter(t => t.type === 'blob').map(t => t.path);
125
- const samplesToFetch = [];
126
-
127
- // Match files against patterns and select up to max per pattern
128
- for (const config of KEY_FILES_TO_SAMPLE) {
129
- const matches = files.filter(f => config.pattern.test(f));
130
- const selected = matches.slice(0, config.max);
131
- for (const path of selected) {
132
- samplesToFetch.push({ path, purpose: config.purpose });
133
- }
134
- }
135
-
136
- // Fetch content for selected files (limit total to avoid timeout)
137
- const maxTotalFiles = 10;
138
- const filesToFetch = samplesToFetch.slice(0, maxTotalFiles);
139
-
140
- const samples = await Promise.all(
141
- filesToFetch.map(async ({ path, purpose }) => {
142
- try {
143
- const res = await httpsGet(
144
- `/repos/${owner}/${repo}/contents/${encodeURIComponent(path)}`,
145
- accessToken
146
- );
147
- if (res.statusCode === 200 && res.body?.content) {
148
- const content = Buffer.from(res.body.content, 'base64').toString('utf8');
149
- return { path, purpose, content };
150
- }
151
- } catch (e) {
152
- console.log(`Failed to fetch ${path}:`, e.message);
153
- }
154
- return null;
155
- })
156
- );
157
-
158
- return samples.filter(Boolean);
159
- }
160
-
161
- /**
162
- * Analyze code samples using LLM for pattern detection
163
- */
164
- async function analyzeCodeWithLLM(samples, context) {
165
- if (samples.length === 0) {
166
- return { success: false, patterns: [], anti_patterns: [], recommendations: [] };
167
- }
168
-
169
- const detector = new LLMPatternDetector({ maxTokens: 2048 });
170
-
171
- // Build combined code context
172
- const codeContext = samples.map(s =>
173
- `=== ${s.path} (${s.purpose}) ===\n${s.content.substring(0, 5000)}`
174
- ).join('\n\n');
175
-
176
- return await detector.analyzeRepoPatterns(codeContext, context);
177
- }
178
-
179
- /**
180
- * Generate standards recommendations based on discoveries and LLM analysis
181
- */
182
- function generateStandardsRecommendations(discoveries, llmAnalysis) {
183
- const recommendedCategories = new Map();
184
-
185
- // Map tech stack discoveries to standards categories
186
- for (const discovery of discoveries) {
187
- if (discovery.discovery_type === 'tech_stack') {
188
- // Extract tech name from pattern_name (e.g., "Primary language: JavaScript" -> "JavaScript")
189
- const techName = discovery.pattern_name.split(': ').pop();
190
- const categories = TECH_TO_STANDARDS[techName] || [];
191
-
192
- for (const cat of categories) {
193
- if (!recommendedCategories.has(cat)) {
194
- recommendedCategories.set(cat, {
195
- category: cat,
196
- reasons: [],
197
- relevance: 0
198
- });
199
- }
200
- const rec = recommendedCategories.get(cat);
201
- rec.reasons.push(`Uses ${techName}`);
202
- rec.relevance += discovery.confidence;
203
- }
204
- }
205
- }
206
-
207
- // Add LLM recommendations
208
- if (llmAnalysis?.recommendations) {
209
- for (const rec of llmAnalysis.recommendations) {
210
- const cat = rec.category;
211
- if (!recommendedCategories.has(cat)) {
212
- recommendedCategories.set(cat, {
213
- category: cat,
214
- reasons: [],
215
- relevance: 0
216
- });
217
- }
218
- const existing = recommendedCategories.get(cat);
219
- existing.reasons.push(rec.reason);
220
- existing.relevance += rec.priority === 'high' ? 1 : rec.priority === 'medium' ? 0.5 : 0.25;
221
- }
222
- }
223
-
224
- // Sort by relevance and return
225
- return Array.from(recommendedCategories.values())
226
- .sort((a, b) => b.relevance - a.relevance)
227
- .slice(0, 5) // Top 5 recommendations
228
- .map(r => ({
229
- category: r.category,
230
- reasons: [...new Set(r.reasons)].slice(0, 3),
231
- relevance_score: Math.min(r.relevance, 1)
232
- }));
233
- }
234
-
235
- async function githubDiscoverPatterns({ body, requestContext }) {
236
- try {
237
- const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
238
-
239
- if (!email) {
240
- return createErrorResponse(401, 'Authentication required');
241
- }
242
-
243
- const { project_id, owner, repo, branch } = body;
244
- if (!project_id || !owner || !repo) {
245
- return createErrorResponse(400, 'project_id, owner, and repo are required');
246
- }
247
-
248
- const targetBranch = branch || 'main';
249
-
250
- // Verify user has access to project (collaborator or company member)
251
- const projectAccess = await verifyProjectAccess(project_id, email);
252
- if (!projectAccess) {
253
- return createErrorResponse(403, 'Access denied to project');
254
- }
255
-
256
- // Get decrypted token
257
- const connResult = await executeQuery(`
258
- SELECT access_token_encrypted FROM rapport.github_connections
259
- WHERE email_address = $1 AND revoked = FALSE
260
- `, [email]);
261
-
262
- if (connResult.rowCount === 0) {
263
- return createErrorResponse(404, 'No GitHub connection found');
264
- }
265
-
266
- const encryptionKey = process.env.GITHUB_TOKEN_ENCRYPTION_KEY;
267
- const accessToken = decryptToken(connResult.rows[0].access_token_encrypted, encryptionKey);
268
-
269
- // Fetch repo data in parallel
270
- const [languagesRes, treeRes, commitsRes] = await Promise.all([
271
- httpsGet(`/repos/${owner}/${repo}/languages`, accessToken),
272
- httpsGet(`/repos/${owner}/${repo}/git/trees/${targetBranch}?recursive=1`, accessToken),
273
- httpsGet(`/repos/${owner}/${repo}/commits?per_page=50`, accessToken)
274
- ]);
275
-
276
- const discoveries = [];
277
-
278
- // --- Analyze Languages (tech_stack) ---
279
- if (languagesRes.statusCode === 200 && languagesRes.body) {
280
- const languages = languagesRes.body;
281
- const totalBytes = Object.values(languages).reduce((a, b) => a + b, 0);
282
-
283
- for (const [lang, bytes] of Object.entries(languages)) {
284
- const percentage = bytes / totalBytes;
285
- if (percentage >= 0.05) { // At least 5% of codebase
286
- discoveries.push({
287
- discovery_type: 'tech_stack',
288
- pattern_name: `Primary language: ${lang}`,
289
- pattern_description: `${lang} makes up ${Math.round(percentage * 100)}% of the codebase`,
290
- confidence: Math.min(percentage * 2, 0.99),
291
- evidence: [{ source: 'languages_api', percentage: Math.round(percentage * 100) }]
292
- });
293
- }
294
- }
295
- }
296
-
297
- // --- Analyze File Tree (architecture, testing, ci_cd, naming) ---
298
- if (treeRes.statusCode === 200 && treeRes.body?.tree) {
299
- const tree = treeRes.body.tree;
300
- const files = tree.filter(t => t.type === 'blob').map(t => t.path);
301
- const dirs = tree.filter(t => t.type === 'tree').map(t => t.path);
302
-
303
- // Detect test framework
304
- const testFiles = files.filter(f =>
305
- f.includes('.test.') || f.includes('.spec.') || f.includes('__tests__/')
306
- );
307
- if (testFiles.length > 0) {
308
- const isJest = testFiles.some(f => f.endsWith('.test.js') || f.endsWith('.test.ts'));
309
- const isMocha = files.some(f => f.includes('.mocharc') || f.includes('mocha'));
310
- const isPytest = testFiles.some(f => f.startsWith('test_') || f.includes('/test_'));
311
-
312
- let framework = 'Unknown';
313
- if (isJest) framework = 'Jest';
314
- else if (isMocha) framework = 'Mocha';
315
- else if (isPytest) framework = 'pytest';
316
-
317
- discoveries.push({
318
- discovery_type: 'testing',
319
- pattern_name: `Test framework: ${framework}`,
320
- pattern_description: `Found ${testFiles.length} test files using ${framework} conventions`,
321
- confidence: Math.min(testFiles.length / 10, 0.95),
322
- evidence: [{ source: 'file_tree', test_files_count: testFiles.length, sample: testFiles.slice(0, 5) }]
323
- });
324
- }
325
-
326
- // Detect CI/CD
327
- const ciFiles = files.filter(f =>
328
- f.startsWith('.github/workflows/') || f === '.gitlab-ci.yml' ||
329
- f === 'Jenkinsfile' || f === '.circleci/config.yml'
330
- );
331
- if (ciFiles.length > 0) {
332
- const ciTool = ciFiles[0].startsWith('.github/') ? 'GitHub Actions' :
333
- ciFiles[0].includes('gitlab') ? 'GitLab CI' :
334
- ciFiles[0] === 'Jenkinsfile' ? 'Jenkins' : 'CircleCI';
335
-
336
- discoveries.push({
337
- discovery_type: 'ci_cd',
338
- pattern_name: `CI/CD: ${ciTool}`,
339
- pattern_description: `Uses ${ciTool} with ${ciFiles.length} workflow file(s)`,
340
- confidence: 0.95,
341
- evidence: [{ source: 'file_tree', ci_files: ciFiles }]
342
- });
343
- }
344
-
345
- // Detect Docker
346
- const dockerFiles = files.filter(f =>
347
- f === 'Dockerfile' || f === 'docker-compose.yml' || f === 'docker-compose.yaml' ||
348
- f.endsWith('/Dockerfile')
349
- );
350
- if (dockerFiles.length > 0) {
351
- discoveries.push({
352
- discovery_type: 'ci_cd',
353
- pattern_name: 'Containerization: Docker',
354
- pattern_description: `Found ${dockerFiles.length} Docker configuration file(s)`,
355
- confidence: 0.9,
356
- evidence: [{ source: 'file_tree', docker_files: dockerFiles }]
357
- });
358
- }
359
-
360
- // Detect architecture patterns
361
- const srcDirs = dirs.filter(d => d.split('/').length <= 2);
362
- const hasSrcDir = srcDirs.some(d => d === 'src' || d.startsWith('src/'));
363
- const hasLibDir = srcDirs.some(d => d === 'lib' || d.startsWith('lib/'));
364
- const hasComponentsDir = dirs.some(d => d.includes('components'));
365
- const hasPagesDir = dirs.some(d => d.includes('pages') || d.includes('views'));
366
- const hasHandlersDir = dirs.some(d => d.includes('handlers') || d.includes('controllers'));
367
-
368
- if (hasComponentsDir && hasPagesDir) {
369
- discoveries.push({
370
- discovery_type: 'architecture',
371
- pattern_name: 'Frontend: Component-based architecture',
372
- pattern_description: 'Uses components/ and pages/ directory structure (React/Vue/Svelte pattern)',
373
- confidence: 0.85,
374
- evidence: [{ source: 'file_tree', pattern: 'components_pages' }]
375
- });
376
- }
377
-
378
- if (hasHandlersDir) {
379
- discoveries.push({
380
- discovery_type: 'architecture',
381
- pattern_name: 'Backend: Handler/Controller pattern',
382
- pattern_description: 'Uses handlers/ or controllers/ for request handling',
383
- confidence: 0.8,
384
- evidence: [{ source: 'file_tree', pattern: 'handlers_controllers' }]
385
- });
386
- }
387
-
388
- // Detect naming conventions
389
- const jsFiles = files.filter(f => f.endsWith('.js') || f.endsWith('.ts'));
390
- const camelCaseFiles = jsFiles.filter(f => {
391
- const name = f.split('/').pop().replace(/\.(js|ts|tsx|jsx)$/, '');
392
- return /^[a-z][a-zA-Z]*$/.test(name);
393
- });
394
- const kebabCaseFiles = jsFiles.filter(f => {
395
- const name = f.split('/').pop().replace(/\.(js|ts|tsx|jsx)$/, '');
396
- return /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/.test(name);
397
- });
398
- const pascalCaseFiles = jsFiles.filter(f => {
399
- const name = f.split('/').pop().replace(/\.(js|ts|tsx|jsx)$/, '');
400
- return /^[A-Z][a-zA-Z]*$/.test(name);
401
- });
402
-
403
- if (jsFiles.length > 5) {
404
- let namingConvention = 'mixed';
405
- let confidence = 0.5;
406
- const total = jsFiles.length;
407
-
408
- if (camelCaseFiles.length / total > 0.6) {
409
- namingConvention = 'camelCase';
410
- confidence = camelCaseFiles.length / total;
411
- } else if (kebabCaseFiles.length / total > 0.6) {
412
- namingConvention = 'kebab-case';
413
- confidence = kebabCaseFiles.length / total;
414
- } else if (pascalCaseFiles.length / total > 0.6) {
415
- namingConvention = 'PascalCase';
416
- confidence = pascalCaseFiles.length / total;
417
- }
418
-
419
- if (namingConvention !== 'mixed') {
420
- discoveries.push({
421
- discovery_type: 'naming',
422
- pattern_name: `File naming: ${namingConvention}`,
423
- pattern_description: `${Math.round(confidence * 100)}% of JS/TS files use ${namingConvention} naming`,
424
- confidence: Math.min(confidence, 0.95),
425
- evidence: [{ source: 'file_tree', convention: namingConvention, sample_count: jsFiles.length }]
426
- });
427
- }
428
- }
429
-
430
- // Detect TypeScript
431
- const tsFiles = files.filter(f => f.endsWith('.ts') || f.endsWith('.tsx'));
432
- const tsconfigExists = files.some(f => f === 'tsconfig.json');
433
- if (tsFiles.length > 0 && tsconfigExists) {
434
- discoveries.push({
435
- discovery_type: 'tech_stack',
436
- pattern_name: 'TypeScript enabled',
437
- pattern_description: `Found ${tsFiles.length} TypeScript files with tsconfig.json`,
438
- confidence: 0.95,
439
- evidence: [{ source: 'file_tree', ts_files_count: tsFiles.length }]
440
- });
441
- }
442
- }
443
-
444
- // --- Analyze package.json if available ---
445
- const pkgRes = await httpsGet(`/repos/${owner}/${repo}/contents/package.json`, accessToken);
446
- if (pkgRes.statusCode === 200 && pkgRes.body?.content) {
447
- try {
448
- const pkgContent = JSON.parse(Buffer.from(pkgRes.body.content, 'base64').toString());
449
-
450
- // Detect frameworks from dependencies
451
- const allDeps = { ...pkgContent.dependencies, ...pkgContent.devDependencies };
452
- const frameworks = [];
453
-
454
- if (allDeps['react']) frameworks.push('React');
455
- if (allDeps['next']) frameworks.push('Next.js');
456
- if (allDeps['vue']) frameworks.push('Vue.js');
457
- if (allDeps['express']) frameworks.push('Express');
458
- if (allDeps['fastify']) frameworks.push('Fastify');
459
- if (allDeps['@angular/core']) frameworks.push('Angular');
460
- if (allDeps['svelte']) frameworks.push('Svelte');
461
- if (allDeps['tailwindcss']) frameworks.push('Tailwind CSS');
462
-
463
- for (const fw of frameworks) {
464
- discoveries.push({
465
- discovery_type: 'tech_stack',
466
- pattern_name: `Framework: ${fw}`,
467
- pattern_description: `${fw} is listed as a dependency`,
468
- confidence: 0.95,
469
- evidence: [{ source: 'package_json', dependency: fw.toLowerCase() }]
470
- });
471
- }
472
-
473
- // Detect test runner from devDeps
474
- if (allDeps['jest'] || allDeps['@jest/core']) {
475
- // Only add if not already detected from file tree
476
- if (!discoveries.some(d => d.pattern_name.includes('Jest'))) {
477
- discoveries.push({
478
- discovery_type: 'testing',
479
- pattern_name: 'Test framework: Jest',
480
- pattern_description: 'Jest is configured as a dev dependency',
481
- confidence: 0.9,
482
- evidence: [{ source: 'package_json' }]
483
- });
484
- }
485
- }
486
- } catch (e) {
487
- console.log('Failed to parse package.json:', e.message);
488
- }
489
- }
490
-
491
- // --- Analyze Commit Patterns ---
492
- if (commitsRes.statusCode === 200 && Array.isArray(commitsRes.body)) {
493
- const commits = commitsRes.body;
494
-
495
- // Detect conventional commits
496
- const conventionalPattern = /^(feat|fix|chore|docs|style|refactor|test|perf|ci|build|revert)(\(.+\))?: /;
497
- const conventionalCount = commits.filter(c =>
498
- conventionalPattern.test(c.commit?.message || '')
499
- ).length;
500
-
501
- if (conventionalCount > commits.length * 0.3) {
502
- discoveries.push({
503
- discovery_type: 'naming',
504
- pattern_name: 'Commit convention: Conventional Commits',
505
- pattern_description: `${Math.round(conventionalCount / commits.length * 100)}% of recent commits follow conventional commit format`,
506
- confidence: Math.min(conventionalCount / commits.length, 0.95),
507
- evidence: [{ source: 'commits', conventional_count: conventionalCount, total: commits.length }]
508
- });
509
- }
510
- }
511
-
512
- // --- Deep Code Analysis with LLM ---
513
- let llmAnalysis = { success: false, patterns: [], anti_patterns: [], recommendations: [] };
514
- let codeSamples = [];
515
-
516
- if (treeRes.statusCode === 200 && treeRes.body?.tree) {
517
- try {
518
- // Fetch representative code samples
519
- codeSamples = await fetchSampleFiles(treeRes.body.tree, accessToken, owner, repo);
520
-
521
- if (codeSamples.length > 0) {
522
- // Build context for LLM
523
- const techStack = discoveries
524
- .filter(d => d.discovery_type === 'tech_stack')
525
- .map(d => d.pattern_name.split(': ').pop());
526
-
527
- const frameworks = discoveries
528
- .filter(d => d.pattern_name.includes('Framework:'))
529
- .map(d => d.pattern_name.split(': ').pop());
530
-
531
- llmAnalysis = await analyzeCodeWithLLM(codeSamples, {
532
- repoName: `${owner}/${repo}`,
533
- techStack,
534
- frameworks
535
- });
536
-
537
- // Add LLM-detected patterns as discoveries
538
- if (llmAnalysis.success && llmAnalysis.patterns) {
539
- for (const pattern of llmAnalysis.patterns) {
540
- discoveries.push({
541
- discovery_type: 'code_pattern',
542
- pattern_name: `${pattern.action}: ${pattern.element}`,
543
- pattern_description: pattern.rule,
544
- confidence: pattern.confidence || 0.8,
545
- evidence: [{
546
- source: 'llm_analysis',
547
- category: pattern.category,
548
- evidence: pattern.evidence
549
- }]
550
- });
551
- }
552
- }
553
-
554
- // Add anti-patterns as discoveries
555
- if (llmAnalysis.anti_patterns) {
556
- for (const antiPattern of llmAnalysis.anti_patterns) {
557
- discoveries.push({
558
- discovery_type: 'anti_pattern',
559
- pattern_name: `Anti-pattern: ${antiPattern.pattern.substring(0, 50)}`,
560
- pattern_description: `${antiPattern.risk}. Fix: ${antiPattern.fix}`,
561
- confidence: 0.85,
562
- evidence: [{ source: 'llm_analysis', type: 'anti_pattern' }]
563
- });
564
- }
565
- }
566
- }
567
- } catch (llmError) {
568
- console.log('LLM analysis failed (continuing without):', llmError.message);
569
- }
570
- }
571
-
572
- // Filter out low-confidence discoveries
573
- const validDiscoveries = discoveries.filter(d => d.confidence >= 0.3);
574
-
575
- // Generate standards recommendations
576
- const recommendedStandards = generateStandardsRecommendations(validDiscoveries, llmAnalysis);
577
-
578
- // Update project with GitHub info
579
- await executeQuery(`
580
- UPDATE rapport.projects SET github_owner = $1, github_repo = $2
581
- WHERE project_id = $3
582
- `, [owner, repo, project_id]);
583
-
584
- // Save discoveries to DB and capture generated IDs
585
- for (const discovery of validDiscoveries) {
586
- const insertResult = await executeQuery(`
587
- INSERT INTO rapport.onboarding_discoveries (
588
- project_id, email_address, discovery_type, pattern_name,
589
- pattern_description, confidence, evidence
590
- ) VALUES ($1, $2, $3, $4, $5, $6, $7)
591
- RETURNING discovery_id
592
- `, [
593
- project_id, email, discovery.discovery_type, discovery.pattern_name,
594
- discovery.pattern_description, discovery.confidence,
595
- JSON.stringify(discovery.evidence)
596
- ]);
597
- discovery.discovery_id = insertResult.rows[0]?.discovery_id;
598
- }
599
-
600
- return createSuccessResponse({
601
- discoveries: validDiscoveries,
602
- count: validDiscoveries.length,
603
- llm_analysis: llmAnalysis.success ? {
604
- patterns_detected: llmAnalysis.patterns?.length || 0,
605
- anti_patterns_detected: llmAnalysis.anti_patterns?.length || 0,
606
- tech_summary: llmAnalysis.tech_summary || {}
607
- } : null,
608
- recommended_standards: recommendedStandards,
609
- code_samples_analyzed: codeSamples.length,
610
- onboarding_suggestions: recommendedStandards.slice(0, 3).map(r => ({
611
- category: r.category,
612
- reason: r.reasons[0] || 'Detected in repository'
613
- }))
614
- }, 'Pattern discovery complete');
615
- } catch (error) {
616
- console.error('GitHub Discover Patterns Error:', error);
617
- return createErrorResponse(500, 'Failed to discover patterns');
618
- }
619
- }
620
-
621
- exports.handler = wrapHandler(githubDiscoverPatterns);