@equilateral_ai/mindmeld 3.3.0 → 3.4.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-start.js +636 -42
- 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/subscriptionTiers.js +27 -27
- package/src/handlers/mcp/mcpHandler.js +569 -0
- package/src/handlers/mcp/mindmeldMcpHandler.js +689 -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 +12 -8
- package/src/handlers/webhooks/githubWebhook.js +117 -125
- package/src/index.js +46 -51
|
@@ -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;
|
|
@@ -310,72 +310,64 @@ class MindmeldClient {
|
|
|
310
310
|
try {
|
|
311
311
|
const apiUrl = this.config.apiUrl;
|
|
312
312
|
|
|
313
|
-
// Build query params
|
|
313
|
+
// Build query params (no path parameters per standards)
|
|
314
|
+
// Uses /api/correlations/project which returns pattern effectiveness and team data
|
|
314
315
|
const params = new URLSearchParams();
|
|
315
|
-
|
|
316
|
-
params.append('
|
|
317
|
-
params.append('learningDays', learningDays.toString());
|
|
316
|
+
params.append('project_id', projectId);
|
|
317
|
+
params.append('lookbackDays', learningDays.toString());
|
|
318
318
|
|
|
319
319
|
const response = await this._makeApiRequest(
|
|
320
|
-
`${apiUrl}/api/
|
|
320
|
+
`${apiUrl}/api/correlations/project?${params.toString()}`,
|
|
321
321
|
'GET'
|
|
322
322
|
);
|
|
323
323
|
|
|
324
324
|
if (response.error) {
|
|
325
325
|
console.error('[Rapport] loadProjectContext API error:', response.error);
|
|
326
326
|
// Fall through to local storage
|
|
327
|
-
} else {
|
|
328
|
-
|
|
327
|
+
} else if (response.data) {
|
|
328
|
+
const data = response.data;
|
|
329
|
+
// Return normalized context object from correlations endpoint
|
|
329
330
|
return {
|
|
330
|
-
projectId:
|
|
331
|
-
projectName:
|
|
332
|
-
companyId:
|
|
333
|
-
|
|
334
|
-
// Team patterns
|
|
335
|
-
patterns: (
|
|
336
|
-
patternId: p.pattern_id,
|
|
337
|
-
intent: p.intent,
|
|
338
|
-
element: p.element
|
|
331
|
+
projectId: data.project?.projectId || projectId,
|
|
332
|
+
projectName: data.project?.projectName,
|
|
333
|
+
companyId: data.project?.companyId,
|
|
334
|
+
|
|
335
|
+
// Team patterns from pattern effectiveness data
|
|
336
|
+
patterns: (data.patternEffectiveness || []).slice(0, patternLimit).map(p => ({
|
|
337
|
+
patternId: p.pattern_id || p.patternId,
|
|
338
|
+
intent: p.intent || p.element,
|
|
339
|
+
element: p.element,
|
|
339
340
|
maturity: p.maturity,
|
|
340
|
-
confidence: p.
|
|
341
|
-
usageCount: p.usage_count || p.
|
|
341
|
+
confidence: p.success_rate || p.correlation || 0,
|
|
342
|
+
usageCount: p.usage_count || p.usageCount || 0,
|
|
342
343
|
lastUsed: p.last_used
|
|
343
344
|
})),
|
|
344
345
|
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
confidence: e.confidence,
|
|
353
|
-
isLoadBearing: e.is_load_bearing
|
|
354
|
-
})),
|
|
355
|
-
|
|
356
|
-
// Recent team learning
|
|
357
|
-
recentLearning: (response.recent_learning || []).map(l => ({
|
|
358
|
-
patternId: l.pattern_id,
|
|
359
|
-
element: l.element,
|
|
360
|
-
learnedBy: l.discovered_by || l.email_address,
|
|
361
|
-
learnedAt: l.discovered_at || l.used_at,
|
|
362
|
-
success: l.success
|
|
346
|
+
// Recent correlations as learning events
|
|
347
|
+
recentLearning: (data.recentCorrelations || []).map(c => ({
|
|
348
|
+
patternId: c.session_id,
|
|
349
|
+
element: c.commit_message || 'session',
|
|
350
|
+
learnedBy: c.email,
|
|
351
|
+
learnedAt: c.session_end || c.commit_time,
|
|
352
|
+
success: c.correlation_score > 0.5
|
|
363
353
|
})),
|
|
364
354
|
|
|
365
|
-
//
|
|
366
|
-
collaborators: (
|
|
367
|
-
email:
|
|
368
|
-
role:
|
|
369
|
-
isExternal:
|
|
370
|
-
|
|
355
|
+
// Developer breakdown as collaborator activity
|
|
356
|
+
collaborators: (data.developerBreakdown || []).map(d => ({
|
|
357
|
+
email: d.email,
|
|
358
|
+
role: 'collaborator',
|
|
359
|
+
isExternal: false,
|
|
360
|
+
sessions: d.totalSessions,
|
|
361
|
+
commits: d.totalCommits,
|
|
362
|
+
correlation: d.avgCorrelation
|
|
371
363
|
})),
|
|
372
364
|
|
|
373
|
-
//
|
|
374
|
-
|
|
365
|
+
// Metrics
|
|
366
|
+
metrics: data.metrics,
|
|
375
367
|
|
|
376
368
|
// Metadata
|
|
377
|
-
lastActive:
|
|
378
|
-
sessionCount:
|
|
369
|
+
lastActive: null,
|
|
370
|
+
sessionCount: data.metrics?.totalSessions || 0
|
|
379
371
|
};
|
|
380
372
|
}
|
|
381
373
|
} catch (error) {
|
|
@@ -721,14 +713,14 @@ class MindmeldClient {
|
|
|
721
713
|
* @param {string} sessionId - Session identifier
|
|
722
714
|
* @param {Array} standards - Array of standards that were injected
|
|
723
715
|
*/
|
|
724
|
-
recordStandardsShown(sessionId, standards) {
|
|
716
|
+
recordStandardsShown(sessionId, standards, projectId, userId) {
|
|
725
717
|
// Fire and forget - don't await to keep hook fast
|
|
726
|
-
this._recordStandardsAsync(sessionId, standards).catch(err => {
|
|
718
|
+
this._recordStandardsAsync(sessionId, standards, projectId, userId).catch(err => {
|
|
727
719
|
console.error('[Rapport] recordStandardsShown error:', err.message);
|
|
728
720
|
});
|
|
729
721
|
}
|
|
730
722
|
|
|
731
|
-
async _recordStandardsAsync(sessionId, standards) {
|
|
723
|
+
async _recordStandardsAsync(sessionId, standards, projectId, userId) {
|
|
732
724
|
if (!standards || standards.length === 0) return;
|
|
733
725
|
|
|
734
726
|
const apiUrl = this.config.apiUrl;
|
|
@@ -738,9 +730,12 @@ class MindmeldClient {
|
|
|
738
730
|
// Prepare payload
|
|
739
731
|
const payload = JSON.stringify({
|
|
740
732
|
session_id: sessionId,
|
|
733
|
+
project_id: projectId || undefined,
|
|
734
|
+
user_id: userId || undefined,
|
|
741
735
|
standards: standards.map(s => ({
|
|
742
736
|
pattern_id: s.pattern_id || s.element,
|
|
743
|
-
|
|
737
|
+
title: s.element || s.pattern_id,
|
|
738
|
+
relevance_score: s.relevance_score || s.score || 0
|
|
744
739
|
}))
|
|
745
740
|
});
|
|
746
741
|
|