@equilateral_ai/mindmeld 3.3.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -10
- package/hooks/pre-compact.js +213 -25
- package/hooks/session-end.js +112 -3
- package/hooks/session-start.js +635 -41
- package/hooks/subagent-start.js +150 -0
- package/hooks/subagent-stop.js +184 -0
- package/package.json +8 -7
- package/scripts/init-project.js +74 -33
- package/scripts/mcp-bridge.js +220 -0
- package/src/core/CorrelationAnalyzer.js +157 -0
- package/src/core/LLMPatternDetector.js +198 -0
- package/src/core/RelevanceDetector.js +123 -36
- package/src/core/StandardsIngestion.js +119 -18
- package/src/handlers/activity/activityGetMe.js +1 -1
- package/src/handlers/activity/activityGetTeam.js +100 -55
- package/src/handlers/admin/adminSetup.js +216 -0
- package/src/handlers/alerts/alertsAcknowledge.js +6 -6
- package/src/handlers/alerts/alertsGet.js +11 -11
- package/src/handlers/analytics/activitySummaryGet.js +34 -35
- package/src/handlers/analytics/coachingGet.js +11 -11
- package/src/handlers/analytics/convergenceGet.js +236 -0
- package/src/handlers/analytics/developerScoreGet.js +41 -111
- package/src/handlers/collaborators/collaboratorInvite.js +1 -1
- package/src/handlers/company/companyUsersDelete.js +141 -0
- package/src/handlers/company/companyUsersGet.js +90 -0
- package/src/handlers/company/companyUsersPost.js +267 -0
- package/src/handlers/company/companyUsersPut.js +76 -0
- package/src/handlers/correlations/correlationsDeveloperGet.js +12 -12
- package/src/handlers/correlations/correlationsGet.js +8 -8
- package/src/handlers/correlations/correlationsProjectGet.js +5 -5
- package/src/handlers/enterprise/controlTowerGet.js +224 -0
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +48 -9
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +1 -3
- package/src/handlers/github/githubConnectionStatus.js +1 -1
- package/src/handlers/github/githubDiscoverPatterns.js +4 -2
- package/src/handlers/github/githubPatternsReview.js +7 -36
- package/src/handlers/health/healthGet.js +55 -0
- package/src/handlers/helpers/checkSuperAdmin.js +13 -14
- package/src/handlers/helpers/mindmeldMcpCore.js +594 -0
- package/src/handlers/helpers/subscriptionTiers.js +27 -27
- package/src/handlers/mcp/mcpHandler.js +569 -0
- package/src/handlers/mcp/mindmeldMcpHandler.js +124 -0
- package/src/handlers/mcp/mindmeldMcpStreamHandler.js +243 -0
- package/src/handlers/notifications/sendNotification.js +18 -18
- package/src/handlers/patterns/patternEvaluatePromotionPost.js +173 -0
- package/src/handlers/projects/projectCreate.js +124 -10
- package/src/handlers/projects/projectDelete.js +4 -4
- package/src/handlers/projects/projectGet.js +8 -8
- package/src/handlers/projects/projectUpdate.js +4 -4
- package/src/handlers/reports/aiLeverage.js +34 -30
- package/src/handlers/reports/engineeringInvestment.js +16 -16
- package/src/handlers/reports/riskForecast.js +41 -21
- package/src/handlers/reports/standardsRoi.js +101 -9
- package/src/handlers/scheduled/maturityUpdateJob.js +166 -0
- package/src/handlers/sessions/sessionStandardsPost.js +43 -7
- package/src/handlers/standards/discoveriesGet.js +93 -0
- package/src/handlers/standards/projectStandardsGet.js +2 -2
- package/src/handlers/standards/projectStandardsPut.js +2 -2
- package/src/handlers/standards/standardsRelevantPost.js +107 -12
- package/src/handlers/standards/standardsTransition.js +112 -15
- package/src/handlers/stripe/billingPortalPost.js +1 -1
- package/src/handlers/stripe/enterpriseCheckoutPost.js +2 -2
- package/src/handlers/stripe/subscriptionCreatePost.js +2 -2
- package/src/handlers/stripe/webhookPost.js +42 -14
- package/src/handlers/user/apiTokenCreate.js +71 -0
- package/src/handlers/user/apiTokenList.js +64 -0
- package/src/handlers/user/userSplashGet.js +90 -73
- package/src/handlers/users/cognitoPostConfirmation.js +37 -1
- package/src/handlers/users/cognitoPreSignUp.js +114 -0
- package/src/handlers/users/userGet.js +15 -11
- package/src/handlers/webhooks/githubWebhook.js +117 -125
- package/src/index.js +8 -5
|
@@ -10,16 +10,35 @@ const crypto = require('crypto');
|
|
|
10
10
|
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
11
11
|
|
|
12
12
|
exports.handler = wrapHandler(async ({ body, headers }) => {
|
|
13
|
-
// Verify webhook signature
|
|
13
|
+
// Verify webhook signature — try per-repo secret first, then global fallback
|
|
14
14
|
const signature = headers['x-hub-signature-256'] || headers['X-Hub-Signature-256'];
|
|
15
|
-
const
|
|
15
|
+
const rawBody = JSON.stringify(body);
|
|
16
|
+
const repoFullName = body?.repository?.full_name;
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
let verified = false;
|
|
19
|
+
|
|
20
|
+
// Try per-repo secret from git_repositories
|
|
21
|
+
if (repoFullName) {
|
|
22
|
+
const repoResult = await executeQuery(`
|
|
23
|
+
SELECT webhook_secret FROM rapport.git_repositories
|
|
24
|
+
WHERE repo_name = $1 AND webhook_secret IS NOT NULL
|
|
25
|
+
LIMIT 1
|
|
26
|
+
`, [repoFullName]);
|
|
27
|
+
|
|
28
|
+
if (repoResult.rowCount > 0 && repoResult.rows[0].webhook_secret) {
|
|
29
|
+
verified = verifySignature(rawBody, signature, repoResult.rows[0].webhook_secret);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Fallback to global secret
|
|
34
|
+
if (!verified) {
|
|
35
|
+
const globalSecret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
36
|
+
if (globalSecret) {
|
|
37
|
+
verified = verifySignature(rawBody, signature, globalSecret);
|
|
38
|
+
}
|
|
20
39
|
}
|
|
21
40
|
|
|
22
|
-
if (!
|
|
41
|
+
if (!verified) {
|
|
23
42
|
return createErrorResponse(401, 'Invalid signature');
|
|
24
43
|
}
|
|
25
44
|
|
|
@@ -58,94 +77,93 @@ async function handlePushEvent(payload) {
|
|
|
58
77
|
const repoFullName = payload.repository?.full_name;
|
|
59
78
|
if (!repoFullName) return;
|
|
60
79
|
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
SELECT project_id FROM rapport.projects
|
|
64
|
-
WHERE repo_url LIKE $1
|
|
65
|
-
`, [`%${repoFullName}%`]);
|
|
80
|
+
const repoUrl = payload.repository?.html_url || `https://github.com/${repoFullName}`;
|
|
81
|
+
const branch = payload.ref?.replace('refs/heads/', '') || 'unknown';
|
|
66
82
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
// Find or create repo in git_repositories
|
|
84
|
+
let repoResult = await executeQuery(`
|
|
85
|
+
SELECT repo_id, company_id FROM rapport.git_repositories
|
|
86
|
+
WHERE repo_name = $1 OR repo_url = $2
|
|
87
|
+
`, [repoFullName, repoUrl]);
|
|
88
|
+
|
|
89
|
+
if (repoResult.rowCount === 0) {
|
|
90
|
+
// Look up company from projects table
|
|
91
|
+
const projectResult = await executeQuery(`
|
|
92
|
+
SELECT project_id, company_id FROM rapport.projects
|
|
93
|
+
WHERE repo_url LIKE $1
|
|
94
|
+
LIMIT 1
|
|
95
|
+
`, [`%${repoFullName}%`]);
|
|
96
|
+
|
|
97
|
+
const companyId = projectResult.rows[0]?.company_id || null;
|
|
98
|
+
|
|
99
|
+
// Auto-register the repo
|
|
100
|
+
repoResult = await executeQuery(`
|
|
101
|
+
INSERT INTO rapport.git_repositories (repo_id, repo_name, repo_url, company_id, default_branch, created_at, updated_at)
|
|
102
|
+
VALUES (gen_random_uuid(), $1, $2, $3, $4, NOW(), NOW())
|
|
103
|
+
RETURNING repo_id, company_id
|
|
104
|
+
`, [repoFullName, repoUrl, companyId, branch]);
|
|
105
|
+
|
|
106
|
+
console.log(`Auto-registered repo: ${repoFullName} (repo_id: ${repoResult.rows[0].repo_id})`);
|
|
70
107
|
}
|
|
71
108
|
|
|
72
|
-
const
|
|
73
|
-
const branch = payload.ref?.replace('refs/heads/', '') || 'unknown';
|
|
109
|
+
const repoId = repoResult.rows[0].repo_id;
|
|
74
110
|
|
|
75
111
|
for (const commit of (payload.commits || [])) {
|
|
76
|
-
await recordCommit(commit,
|
|
112
|
+
await recordCommit(commit, repoId, repoFullName, repoUrl);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Update last_commit_sha on the repo
|
|
116
|
+
const lastCommit = payload.head_commit?.id || payload.commits?.[payload.commits.length - 1]?.id;
|
|
117
|
+
if (lastCommit) {
|
|
118
|
+
await executeQuery(`
|
|
119
|
+
UPDATE rapport.git_repositories SET last_commit_sha = $1, updated_at = NOW()
|
|
120
|
+
WHERE repo_id = $2
|
|
121
|
+
`, [lastCommit, repoId]);
|
|
77
122
|
}
|
|
78
123
|
}
|
|
79
124
|
|
|
80
|
-
async function recordCommit(commit,
|
|
125
|
+
async function recordCommit(commit, repoId, repoName, repoUrl) {
|
|
81
126
|
const authorEmail = commit.author?.email;
|
|
82
127
|
if (!authorEmail) return;
|
|
83
128
|
|
|
84
|
-
//
|
|
85
|
-
const
|
|
86
|
-
SELECT
|
|
87
|
-
|
|
88
|
-
`, [authorEmail]);
|
|
129
|
+
// Check for duplicate
|
|
130
|
+
const existing = await executeQuery(`
|
|
131
|
+
SELECT 1 FROM rapport.commits WHERE commit_sha = $1 LIMIT 1
|
|
132
|
+
`, [commit.id]);
|
|
89
133
|
|
|
90
|
-
if (
|
|
91
|
-
console.log(`
|
|
134
|
+
if (existing.rowCount > 0) {
|
|
135
|
+
console.log(`Commit ${commit.id.substring(0, 8)} already recorded, skipping`);
|
|
92
136
|
return;
|
|
93
137
|
}
|
|
94
138
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
WHERE email_address = $1
|
|
102
|
-
AND project_id = $2
|
|
103
|
-
ORDER BY started_at DESC
|
|
104
|
-
LIMIT 1
|
|
105
|
-
`, [email, projectId]);
|
|
106
|
-
|
|
107
|
-
let sessionId = null;
|
|
108
|
-
let withinSession = false;
|
|
109
|
-
let timeSinceSession = null;
|
|
110
|
-
|
|
111
|
-
if (sessionResult.rowCount > 0) {
|
|
112
|
-
const session = sessionResult.rows[0];
|
|
113
|
-
const commitTime = new Date(commit.timestamp);
|
|
114
|
-
const sessionEnd = session.ended_at ? new Date(session.ended_at) : new Date();
|
|
115
|
-
const sessionStart = new Date(session.started_at);
|
|
116
|
-
|
|
117
|
-
// Commit within session if after start and before/during end
|
|
118
|
-
withinSession = commitTime >= sessionStart && (!session.ended_at || commitTime <= sessionEnd);
|
|
119
|
-
|
|
120
|
-
if (!withinSession && session.ended_at) {
|
|
121
|
-
timeSinceSession = Math.round((commitTime - sessionEnd) / (1000 * 60));
|
|
122
|
-
}
|
|
139
|
+
// Build changed_files JSONB from GitHub payload
|
|
140
|
+
const changedFiles = [
|
|
141
|
+
...(commit.added || []).map(f => ({ path: f, action: 'added' })),
|
|
142
|
+
...(commit.modified || []).map(f => ({ path: f, action: 'modified' })),
|
|
143
|
+
...(commit.removed || []).map(f => ({ path: f, action: 'removed' }))
|
|
144
|
+
];
|
|
123
145
|
|
|
124
|
-
sessionId = session.session_id;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Insert commit
|
|
128
146
|
await executeQuery(`
|
|
129
147
|
INSERT INTO rapport.commits (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
134
|
-
ON CONFLICT (commit_hash) DO NOTHING
|
|
148
|
+
commit_id, commit_sha, repo_id, repo_url, repo_name,
|
|
149
|
+
author_email, author_name, commit_timestamp, message,
|
|
150
|
+
changed_files, is_merge, created_at
|
|
151
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, NOW())
|
|
135
152
|
`, [
|
|
136
153
|
commit.id,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
commit.id,
|
|
155
|
+
repoId,
|
|
156
|
+
repoUrl,
|
|
157
|
+
repoName,
|
|
158
|
+
authorEmail,
|
|
159
|
+
commit.author?.name || authorEmail,
|
|
141
160
|
commit.timestamp,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
timeSinceSession
|
|
161
|
+
commit.message?.substring(0, 2000),
|
|
162
|
+
JSON.stringify(changedFiles),
|
|
163
|
+
commit.message?.toLowerCase().startsWith('merge') || false
|
|
146
164
|
]);
|
|
147
165
|
|
|
148
|
-
console.log(`Recorded commit ${commit.id}
|
|
166
|
+
console.log(`Recorded commit ${commit.id.substring(0, 8)} by ${authorEmail}`);
|
|
149
167
|
}
|
|
150
168
|
|
|
151
169
|
async function handlePREvent(payload) {
|
|
@@ -153,71 +171,45 @@ async function handlePREvent(payload) {
|
|
|
153
171
|
const repoFullName = payload.repository?.full_name;
|
|
154
172
|
if (!pr || !repoFullName) return;
|
|
155
173
|
|
|
156
|
-
|
|
157
|
-
const projectResult = await executeQuery(`
|
|
158
|
-
SELECT project_id FROM rapport.projects
|
|
159
|
-
WHERE repo_url LIKE $1
|
|
160
|
-
`, [`%${repoFullName}%`]);
|
|
161
|
-
|
|
162
|
-
if (projectResult.rowCount === 0) {
|
|
163
|
-
console.log(`Project not found for repo: ${repoFullName}`);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
174
|
+
const repoUrl = payload.repository?.html_url || `https://github.com/${repoFullName}`;
|
|
166
175
|
|
|
167
|
-
|
|
168
|
-
const
|
|
176
|
+
// Find repo
|
|
177
|
+
const repoResult = await executeQuery(`
|
|
178
|
+
SELECT repo_id FROM rapport.git_repositories
|
|
179
|
+
WHERE repo_name = $1 OR repo_url = $2
|
|
180
|
+
`, [repoFullName, repoUrl]);
|
|
169
181
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const userResult = await executeQuery(`
|
|
173
|
-
SELECT "Email_Address" FROM "Users"
|
|
174
|
-
WHERE "Email_Address" = $1 AND active = true
|
|
175
|
-
`, [authorEmail]);
|
|
176
|
-
|
|
177
|
-
if (userResult.rowCount === 0) {
|
|
178
|
-
console.log(`User not found for: ${authorEmail}`);
|
|
182
|
+
if (repoResult.rowCount === 0) {
|
|
183
|
+
console.log(`Repo not found for: ${repoFullName} — push event must arrive first`);
|
|
179
184
|
return;
|
|
180
185
|
}
|
|
181
186
|
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
let reviewTimeHours = null;
|
|
186
|
-
if (pr.merged_at && pr.created_at) {
|
|
187
|
-
const created = new Date(pr.created_at);
|
|
188
|
-
const merged = new Date(pr.merged_at);
|
|
189
|
-
reviewTimeHours = (merged - created) / (1000 * 60 * 60);
|
|
190
|
-
}
|
|
187
|
+
const repoId = repoResult.rows[0].repo_id;
|
|
188
|
+
const authorEmail = pr.user?.email || `${pr.user?.login}@users.noreply.github.com`;
|
|
189
|
+
const status = pr.merged ? 'merged' : pr.state;
|
|
191
190
|
|
|
192
|
-
// Upsert PR
|
|
191
|
+
// Upsert PR by repo_id + pr_number
|
|
193
192
|
await executeQuery(`
|
|
194
193
|
INSERT INTO rapport.pull_requests (
|
|
195
|
-
pr_id,
|
|
196
|
-
status,
|
|
197
|
-
|
|
198
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
|
|
199
|
-
ON CONFLICT (pr_id) DO
|
|
200
|
-
status = EXCLUDED.status,
|
|
201
|
-
merged_at = EXCLUDED.merged_at,
|
|
202
|
-
closed_at = EXCLUDED.closed_at,
|
|
203
|
-
review_time_hours = EXCLUDED.review_time_hours
|
|
194
|
+
pr_id, repo_id, pr_number, title, author_email,
|
|
195
|
+
status, created_at, merged_at, closed_at,
|
|
196
|
+
lines_added, lines_removed, files_changed, review_comments
|
|
197
|
+
) VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
198
|
+
ON CONFLICT (pr_id) DO NOTHING
|
|
204
199
|
`, [
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
email,
|
|
200
|
+
repoId,
|
|
201
|
+
pr.number,
|
|
208
202
|
pr.title?.substring(0, 500),
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
pr.merged ? 'merged' : pr.state,
|
|
203
|
+
authorEmail,
|
|
204
|
+
status,
|
|
212
205
|
pr.created_at,
|
|
213
|
-
pr.merged_at,
|
|
214
|
-
pr.closed_at,
|
|
215
|
-
pr.
|
|
216
|
-
pr.
|
|
217
|
-
pr.
|
|
218
|
-
pr.
|
|
219
|
-
reviewTimeHours
|
|
206
|
+
pr.merged_at || null,
|
|
207
|
+
pr.closed_at || null,
|
|
208
|
+
pr.additions || 0,
|
|
209
|
+
pr.deletions || 0,
|
|
210
|
+
pr.changed_files || 0,
|
|
211
|
+
pr.review_comments || 0
|
|
220
212
|
]);
|
|
221
213
|
|
|
222
|
-
console.log(`Recorded PR ${
|
|
214
|
+
console.log(`Recorded PR #${pr.number} (${status}) for ${authorEmail}`);
|
|
223
215
|
}
|
package/src/index.js
CHANGED
|
@@ -65,7 +65,7 @@ class MindmeldClient {
|
|
|
65
65
|
companyId: config.companyId || null,
|
|
66
66
|
collaborators: config.collaborators || [],
|
|
67
67
|
userEmail: config.userEmail || null,
|
|
68
|
-
subscriptionTier: config.subscriptionTier || '
|
|
68
|
+
subscriptionTier: config.subscriptionTier || 'enterprise'
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
return this.currentProject;
|
|
@@ -713,14 +713,14 @@ class MindmeldClient {
|
|
|
713
713
|
* @param {string} sessionId - Session identifier
|
|
714
714
|
* @param {Array} standards - Array of standards that were injected
|
|
715
715
|
*/
|
|
716
|
-
recordStandardsShown(sessionId, standards) {
|
|
716
|
+
recordStandardsShown(sessionId, standards, projectId, userId) {
|
|
717
717
|
// Fire and forget - don't await to keep hook fast
|
|
718
|
-
this._recordStandardsAsync(sessionId, standards).catch(err => {
|
|
718
|
+
this._recordStandardsAsync(sessionId, standards, projectId, userId).catch(err => {
|
|
719
719
|
console.error('[Rapport] recordStandardsShown error:', err.message);
|
|
720
720
|
});
|
|
721
721
|
}
|
|
722
722
|
|
|
723
|
-
async _recordStandardsAsync(sessionId, standards) {
|
|
723
|
+
async _recordStandardsAsync(sessionId, standards, projectId, userId) {
|
|
724
724
|
if (!standards || standards.length === 0) return;
|
|
725
725
|
|
|
726
726
|
const apiUrl = this.config.apiUrl;
|
|
@@ -730,9 +730,12 @@ class MindmeldClient {
|
|
|
730
730
|
// Prepare payload
|
|
731
731
|
const payload = JSON.stringify({
|
|
732
732
|
session_id: sessionId,
|
|
733
|
+
project_id: projectId || undefined,
|
|
734
|
+
user_id: userId || undefined,
|
|
733
735
|
standards: standards.map(s => ({
|
|
734
736
|
pattern_id: s.pattern_id || s.element,
|
|
735
|
-
|
|
737
|
+
title: s.element || s.pattern_id,
|
|
738
|
+
relevance_score: s.relevance_score || s.score || 0
|
|
736
739
|
}))
|
|
737
740
|
});
|
|
738
741
|
|