@equilateral_ai/mindmeld 3.5.3 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/hooks/session-start.js +312 -85
  2. package/package.json +21 -13
  3. package/scripts/init-project.js +9 -23
  4. package/scripts/repo-analyzer.js +118 -2
  5. package/src/client/dbShim.js +16 -0
  6. package/src/core/AuthManager.js +3 -2
  7. package/src/handlers/helpers/dbOperations.js +9 -46
  8. package/src/index.js +2 -217
  9. package/src/utils/piiMask.js +16 -0
  10. package/scripts/inject.js +0 -409
  11. package/scripts/mcp-bridge.js +0 -220
  12. package/scripts/standards.js +0 -285
  13. package/src/collaboration/CollaborationPrompt.js +0 -460
  14. package/src/core/AlertEngine.js +0 -813
  15. package/src/core/AlertNotifier.js +0 -363
  16. package/src/core/CorrelationAnalyzer.js +0 -931
  17. package/src/core/CrossReferenceEngine.js +0 -624
  18. package/src/core/CurationEngine.js +0 -688
  19. package/src/core/DeprecationScheduler.js +0 -183
  20. package/src/core/LoadBearingDetector.js +0 -242
  21. package/src/core/NotificationService.js +0 -1032
  22. package/src/core/RapportOrchestrator.js +0 -632
  23. package/src/core/RelevanceDetector.js +0 -694
  24. package/src/core/StandardLifecycle.js +0 -244
  25. package/src/core/StandardsIngestion.js +0 -991
  26. package/src/core/TeamLoadBearingDetector.js +0 -431
  27. package/src/core/parsers/adrParser.js +0 -479
  28. package/src/core/parsers/cursorRulesParser.js +0 -564
  29. package/src/core/parsers/eslintParser.js +0 -439
  30. package/src/database/dbOperations.js +0 -105
  31. package/src/handlers/activity/activityGetMe.js +0 -98
  32. package/src/handlers/activity/activityGetTeam.js +0 -175
  33. package/src/handlers/admin/adminSetup.js +0 -216
  34. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  35. package/src/handlers/alerts/alertsGet.js +0 -250
  36. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  37. package/src/handlers/analytics/coachingGet.js +0 -361
  38. package/src/handlers/analytics/convergenceGet.js +0 -236
  39. package/src/handlers/analytics/developerScoreGet.js +0 -137
  40. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  41. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  42. package/src/handlers/collaborators/collaboratorList.js +0 -82
  43. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  44. package/src/handlers/collaborators/inviteAccept.js +0 -122
  45. package/src/handlers/company/companyUsersDelete.js +0 -141
  46. package/src/handlers/company/companyUsersGet.js +0 -90
  47. package/src/handlers/company/companyUsersPost.js +0 -267
  48. package/src/handlers/company/companyUsersPut.js +0 -76
  49. package/src/handlers/context/contextGet.js +0 -57
  50. package/src/handlers/context/invariantsGet.js +0 -74
  51. package/src/handlers/context/loopsGet.js +0 -82
  52. package/src/handlers/context/notesCreate.js +0 -74
  53. package/src/handlers/context/purposeGet.js +0 -78
  54. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  55. package/src/handlers/correlations/correlationsGet.js +0 -93
  56. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  57. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  58. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  59. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  60. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  61. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  62. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  63. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  64. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  65. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  66. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  67. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  68. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  69. package/src/handlers/github/githubConnectionStatus.js +0 -49
  70. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  71. package/src/handlers/github/githubOAuthCallback.js +0 -178
  72. package/src/handlers/github/githubOAuthStart.js +0 -59
  73. package/src/handlers/github/githubPatternsReview.js +0 -76
  74. package/src/handlers/github/githubReposList.js +0 -105
  75. package/src/handlers/health/healthGet.js +0 -55
  76. package/src/handlers/helpers/auditLogger.js +0 -201
  77. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  78. package/src/handlers/helpers/decisionFrames.js +0 -29
  79. package/src/handlers/helpers/errorHandler.js +0 -49
  80. package/src/handlers/helpers/index.js +0 -138
  81. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  82. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  83. package/src/handlers/helpers/predictiveCache.js +0 -51
  84. package/src/handlers/helpers/projectAccess.js +0 -88
  85. package/src/handlers/helpers/responseUtil.js +0 -55
  86. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  87. package/src/handlers/mcp/mcpHandler.js +0 -569
  88. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  89. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  90. package/src/handlers/notifications/getPreferences.js +0 -84
  91. package/src/handlers/notifications/sendNotification.js +0 -170
  92. package/src/handlers/notifications/updatePreferences.js +0 -316
  93. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  94. package/src/handlers/patterns/patternUsagePost.js +0 -182
  95. package/src/handlers/patterns/patternViolationPost.js +0 -185
  96. package/src/handlers/projects/projectCreate.js +0 -248
  97. package/src/handlers/projects/projectDelete.js +0 -82
  98. package/src/handlers/projects/projectGet.js +0 -95
  99. package/src/handlers/projects/projectUpdate.js +0 -117
  100. package/src/handlers/reports/aiLeverage.js +0 -210
  101. package/src/handlers/reports/engineeringInvestment.js +0 -132
  102. package/src/handlers/reports/riskForecast.js +0 -206
  103. package/src/handlers/reports/standardsRoi.js +0 -254
  104. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  105. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  106. package/src/handlers/scheduled/generateAlerts.js +0 -135
  107. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  108. package/src/handlers/scheduled/refreshActivity.js +0 -21
  109. package/src/handlers/scheduled/scanCompliance.js +0 -334
  110. package/src/handlers/sessions/sessionEndPost.js +0 -180
  111. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  112. package/src/handlers/standards/catalogGet.js +0 -185
  113. package/src/handlers/standards/catalogSync.js +0 -120
  114. package/src/handlers/standards/discoveriesGet.js +0 -89
  115. package/src/handlers/standards/projectStandardsGet.js +0 -129
  116. package/src/handlers/standards/projectStandardsPut.js +0 -151
  117. package/src/handlers/standards/standardsAuditGet.js +0 -65
  118. package/src/handlers/standards/standardsParseUpload.js +0 -149
  119. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  120. package/src/handlers/standards/standardsTransition.js +0 -161
  121. package/src/handlers/stripe/addonManagePost.js +0 -240
  122. package/src/handlers/stripe/billingPortalPost.js +0 -93
  123. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  124. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  125. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  126. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  127. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  128. package/src/handlers/stripe/webhookPost.js +0 -482
  129. package/src/handlers/user/apiTokenCreate.js +0 -71
  130. package/src/handlers/user/apiTokenList.js +0 -64
  131. package/src/handlers/user/userSplashAck.js +0 -91
  132. package/src/handlers/user/userSplashGet.js +0 -211
  133. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  134. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  135. package/src/handlers/users/userEntitlementsGet.js +0 -89
  136. package/src/handlers/users/userGet.js +0 -118
  137. package/src/handlers/users/userProfilePut.js +0 -77
  138. package/src/handlers/webhooks/githubWebhook.js +0 -215
@@ -1,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);