@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.
- package/hooks/session-start.js +312 -85
- package/package.json +21 -13
- package/scripts/init-project.js +9 -23
- package/scripts/repo-analyzer.js +118 -2
- package/src/client/dbShim.js +16 -0
- package/src/core/AuthManager.js +3 -2
- package/src/handlers/helpers/dbOperations.js +9 -46
- package/src/index.js +2 -217
- package/src/utils/piiMask.js +16 -0
- package/scripts/inject.js +0 -409
- package/scripts/mcp-bridge.js +0 -220
- package/scripts/standards.js +0 -285
- package/src/collaboration/CollaborationPrompt.js +0 -460
- package/src/core/AlertEngine.js +0 -813
- package/src/core/AlertNotifier.js +0 -363
- package/src/core/CorrelationAnalyzer.js +0 -931
- package/src/core/CrossReferenceEngine.js +0 -624
- package/src/core/CurationEngine.js +0 -688
- package/src/core/DeprecationScheduler.js +0 -183
- package/src/core/LoadBearingDetector.js +0 -242
- package/src/core/NotificationService.js +0 -1032
- package/src/core/RapportOrchestrator.js +0 -632
- package/src/core/RelevanceDetector.js +0 -694
- package/src/core/StandardLifecycle.js +0 -244
- package/src/core/StandardsIngestion.js +0 -991
- package/src/core/TeamLoadBearingDetector.js +0 -431
- package/src/core/parsers/adrParser.js +0 -479
- package/src/core/parsers/cursorRulesParser.js +0 -564
- package/src/core/parsers/eslintParser.js +0 -439
- package/src/database/dbOperations.js +0 -105
- package/src/handlers/activity/activityGetMe.js +0 -98
- package/src/handlers/activity/activityGetTeam.js +0 -175
- package/src/handlers/admin/adminSetup.js +0 -216
- package/src/handlers/alerts/alertsAcknowledge.js +0 -92
- package/src/handlers/alerts/alertsGet.js +0 -250
- package/src/handlers/analytics/activitySummaryGet.js +0 -234
- package/src/handlers/analytics/coachingGet.js +0 -361
- package/src/handlers/analytics/convergenceGet.js +0 -236
- package/src/handlers/analytics/developerScoreGet.js +0 -137
- package/src/handlers/collaborators/collaboratorAdd.js +0 -200
- package/src/handlers/collaborators/collaboratorInvite.js +0 -219
- package/src/handlers/collaborators/collaboratorList.js +0 -82
- package/src/handlers/collaborators/collaboratorRemove.js +0 -128
- package/src/handlers/collaborators/inviteAccept.js +0 -122
- package/src/handlers/company/companyUsersDelete.js +0 -141
- package/src/handlers/company/companyUsersGet.js +0 -90
- package/src/handlers/company/companyUsersPost.js +0 -267
- package/src/handlers/company/companyUsersPut.js +0 -76
- package/src/handlers/context/contextGet.js +0 -57
- package/src/handlers/context/invariantsGet.js +0 -74
- package/src/handlers/context/loopsGet.js +0 -82
- package/src/handlers/context/notesCreate.js +0 -74
- package/src/handlers/context/purposeGet.js +0 -78
- package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
- package/src/handlers/correlations/correlationsGet.js +0 -93
- package/src/handlers/correlations/correlationsProjectGet.js +0 -153
- package/src/handlers/enterprise/controlTowerGet.js +0 -224
- package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
- package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
- package/src/handlers/github/githubConnectionStatus.js +0 -49
- package/src/handlers/github/githubDiscoverPatterns.js +0 -621
- package/src/handlers/github/githubOAuthCallback.js +0 -178
- package/src/handlers/github/githubOAuthStart.js +0 -59
- package/src/handlers/github/githubPatternsReview.js +0 -76
- package/src/handlers/github/githubReposList.js +0 -105
- package/src/handlers/health/healthGet.js +0 -55
- package/src/handlers/helpers/auditLogger.js +0 -201
- package/src/handlers/helpers/checkSuperAdmin.js +0 -84
- package/src/handlers/helpers/decisionFrames.js +0 -29
- package/src/handlers/helpers/errorHandler.js +0 -49
- package/src/handlers/helpers/index.js +0 -138
- package/src/handlers/helpers/lambdaWrapper.js +0 -60
- package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
- package/src/handlers/helpers/predictiveCache.js +0 -51
- package/src/handlers/helpers/projectAccess.js +0 -88
- package/src/handlers/helpers/responseUtil.js +0 -55
- package/src/handlers/helpers/subscriptionTiers.js +0 -1168
- package/src/handlers/mcp/mcpHandler.js +0 -569
- package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
- package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
- package/src/handlers/notifications/getPreferences.js +0 -84
- package/src/handlers/notifications/sendNotification.js +0 -170
- package/src/handlers/notifications/updatePreferences.js +0 -316
- package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
- package/src/handlers/patterns/patternUsagePost.js +0 -182
- package/src/handlers/patterns/patternViolationPost.js +0 -185
- package/src/handlers/projects/projectCreate.js +0 -248
- package/src/handlers/projects/projectDelete.js +0 -82
- package/src/handlers/projects/projectGet.js +0 -95
- package/src/handlers/projects/projectUpdate.js +0 -117
- package/src/handlers/reports/aiLeverage.js +0 -210
- package/src/handlers/reports/engineeringInvestment.js +0 -132
- package/src/handlers/reports/riskForecast.js +0 -206
- package/src/handlers/reports/standardsRoi.js +0 -254
- package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
- package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
- package/src/handlers/scheduled/generateAlerts.js +0 -135
- package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
- package/src/handlers/scheduled/refreshActivity.js +0 -21
- package/src/handlers/scheduled/scanCompliance.js +0 -334
- package/src/handlers/sessions/sessionEndPost.js +0 -180
- package/src/handlers/sessions/sessionStandardsPost.js +0 -171
- package/src/handlers/standards/catalogGet.js +0 -185
- package/src/handlers/standards/catalogSync.js +0 -120
- package/src/handlers/standards/discoveriesGet.js +0 -89
- package/src/handlers/standards/projectStandardsGet.js +0 -129
- package/src/handlers/standards/projectStandardsPut.js +0 -151
- package/src/handlers/standards/standardsAuditGet.js +0 -65
- package/src/handlers/standards/standardsParseUpload.js +0 -149
- package/src/handlers/standards/standardsRelevantPost.js +0 -405
- package/src/handlers/standards/standardsTransition.js +0 -161
- package/src/handlers/stripe/addonManagePost.js +0 -240
- package/src/handlers/stripe/billingPortalPost.js +0 -93
- package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
- package/src/handlers/stripe/seatsUpdatePost.js +0 -185
- package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
- package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
- package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
- package/src/handlers/stripe/webhookPost.js +0 -482
- package/src/handlers/user/apiTokenCreate.js +0 -71
- package/src/handlers/user/apiTokenList.js +0 -64
- package/src/handlers/user/userSplashAck.js +0 -91
- package/src/handlers/user/userSplashGet.js +0 -211
- package/src/handlers/users/cognitoPostConfirmation.js +0 -186
- package/src/handlers/users/cognitoPreSignUp.js +0 -114
- package/src/handlers/users/userEntitlementsGet.js +0 -89
- package/src/handlers/users/userGet.js +0 -118
- package/src/handlers/users/userProfilePut.js +0 -77
- 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);
|