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