@equilateral_ai/mindmeld 3.5.2 → 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.
- package/hooks/session-end.js +25 -0
- package/hooks/session-start.js +363 -83
- package/hooks/session-watcher.js +585 -0
- package/package.json +19 -13
- package/scripts/init-project.js +9 -23
- 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/harvest.js +0 -601
- package/scripts/inject.js +0 -409
- package/scripts/mcp-bridge.js +0 -220
- package/scripts/repo-analyzer.js +0 -870
- 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,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Update Notification Preferences Handler
|
|
3
|
-
* Updates user's notification preferences
|
|
4
|
-
*
|
|
5
|
-
* PUT /api/notifications/preferences
|
|
6
|
-
* Auth: Cognito JWT required
|
|
7
|
-
*
|
|
8
|
-
* Body:
|
|
9
|
-
* {
|
|
10
|
-
* "email_enabled": true,
|
|
11
|
-
* "slack_enabled": true,
|
|
12
|
-
* "notification_types": {
|
|
13
|
-
* "pattern_promotion": { "email": true, "slack": true },
|
|
14
|
-
* "weekly_digest": { "email": true, "slack": false },
|
|
15
|
-
* "critical_violation": { "email": true, "slack": true },
|
|
16
|
-
* "team_alert": { "email": true, "slack": true },
|
|
17
|
-
* "curation_candidate": { "email": true, "slack": true }
|
|
18
|
-
* },
|
|
19
|
-
* "project_overrides": {
|
|
20
|
-
* "prj_xxx": { "email_enabled": false, "notification_types": {...} }
|
|
21
|
-
* },
|
|
22
|
-
* "digest_frequency": "weekly",
|
|
23
|
-
* "digest_day": 1,
|
|
24
|
-
* "quiet_hours_enabled": false,
|
|
25
|
-
* "quiet_hours_start": "22:00",
|
|
26
|
-
* "quiet_hours_end": "08:00",
|
|
27
|
-
* "quiet_hours_timezone": "America/New_York",
|
|
28
|
-
* "slack_channel": "#my-channel",
|
|
29
|
-
* "slack_dm_enabled": true
|
|
30
|
-
* }
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
34
|
-
|
|
35
|
-
exports.handler = wrapHandler(async ({ requestContext, body }) => {
|
|
36
|
-
const Request_ID = requestContext.requestId;
|
|
37
|
-
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
38
|
-
|
|
39
|
-
if (!email) {
|
|
40
|
-
return createErrorResponse(401, 'Authentication required');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Validate and sanitize input
|
|
44
|
-
const validatedPrefs = validatePreferences(body);
|
|
45
|
-
if (validatedPrefs.error) {
|
|
46
|
-
return createErrorResponse(400, validatedPrefs.error);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Default notification types for new users
|
|
50
|
-
const defaultNotificationTypes = {
|
|
51
|
-
pattern_promotion: { email: true, slack: true },
|
|
52
|
-
weekly_digest: { email: true, slack: false },
|
|
53
|
-
critical_violation: { email: true, slack: true },
|
|
54
|
-
team_alert: { email: true, slack: true },
|
|
55
|
-
curation_candidate: { email: true, slack: true }
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const {
|
|
59
|
-
email_enabled = true,
|
|
60
|
-
slack_enabled = true,
|
|
61
|
-
notification_types = defaultNotificationTypes,
|
|
62
|
-
project_overrides = {},
|
|
63
|
-
digest_frequency = 'weekly',
|
|
64
|
-
digest_day = 1,
|
|
65
|
-
quiet_hours_enabled = false,
|
|
66
|
-
quiet_hours_start,
|
|
67
|
-
quiet_hours_end,
|
|
68
|
-
quiet_hours_timezone = 'America/New_York',
|
|
69
|
-
slack_channel,
|
|
70
|
-
slack_dm_enabled = true
|
|
71
|
-
} = validatedPrefs;
|
|
72
|
-
|
|
73
|
-
// Upsert preferences
|
|
74
|
-
const result = await executeQuery(`
|
|
75
|
-
INSERT INTO rapport.notification_preferences (
|
|
76
|
-
email_address,
|
|
77
|
-
email_enabled,
|
|
78
|
-
slack_enabled,
|
|
79
|
-
notification_types,
|
|
80
|
-
project_overrides,
|
|
81
|
-
digest_frequency,
|
|
82
|
-
digest_day,
|
|
83
|
-
quiet_hours_enabled,
|
|
84
|
-
quiet_hours_start,
|
|
85
|
-
quiet_hours_end,
|
|
86
|
-
quiet_hours_timezone,
|
|
87
|
-
slack_channel,
|
|
88
|
-
slack_dm_enabled
|
|
89
|
-
) VALUES (
|
|
90
|
-
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13
|
|
91
|
-
)
|
|
92
|
-
ON CONFLICT (email_address) DO UPDATE SET
|
|
93
|
-
email_enabled = COALESCE($2, rapport.notification_preferences.email_enabled),
|
|
94
|
-
slack_enabled = COALESCE($3, rapport.notification_preferences.slack_enabled),
|
|
95
|
-
notification_types = COALESCE($4, rapport.notification_preferences.notification_types),
|
|
96
|
-
project_overrides = COALESCE($5, rapport.notification_preferences.project_overrides),
|
|
97
|
-
digest_frequency = COALESCE($6, rapport.notification_preferences.digest_frequency),
|
|
98
|
-
digest_day = COALESCE($7, rapport.notification_preferences.digest_day),
|
|
99
|
-
quiet_hours_enabled = COALESCE($8, rapport.notification_preferences.quiet_hours_enabled),
|
|
100
|
-
quiet_hours_start = $9,
|
|
101
|
-
quiet_hours_end = $10,
|
|
102
|
-
quiet_hours_timezone = COALESCE($11, rapport.notification_preferences.quiet_hours_timezone),
|
|
103
|
-
slack_channel = $12,
|
|
104
|
-
slack_dm_enabled = COALESCE($13, rapport.notification_preferences.slack_dm_enabled),
|
|
105
|
-
updated_at = NOW()
|
|
106
|
-
RETURNING *
|
|
107
|
-
`, [
|
|
108
|
-
email,
|
|
109
|
-
email_enabled,
|
|
110
|
-
slack_enabled,
|
|
111
|
-
notification_types ? JSON.stringify(notification_types) : null,
|
|
112
|
-
project_overrides ? JSON.stringify(project_overrides) : null,
|
|
113
|
-
digest_frequency,
|
|
114
|
-
digest_day,
|
|
115
|
-
quiet_hours_enabled,
|
|
116
|
-
quiet_hours_start,
|
|
117
|
-
quiet_hours_end,
|
|
118
|
-
quiet_hours_timezone,
|
|
119
|
-
slack_channel,
|
|
120
|
-
slack_dm_enabled
|
|
121
|
-
]);
|
|
122
|
-
|
|
123
|
-
// If digest frequency changed, update the digest queue
|
|
124
|
-
if (digest_frequency) {
|
|
125
|
-
await updateDigestQueue(email, digest_frequency, digest_day);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return createSuccessResponse(
|
|
129
|
-
{
|
|
130
|
-
preferences: {
|
|
131
|
-
email_enabled: result.rows[0].email_enabled,
|
|
132
|
-
slack_enabled: result.rows[0].slack_enabled,
|
|
133
|
-
notification_types: result.rows[0].notification_types,
|
|
134
|
-
project_overrides: result.rows[0].project_overrides,
|
|
135
|
-
digest_frequency: result.rows[0].digest_frequency,
|
|
136
|
-
digest_day: result.rows[0].digest_day,
|
|
137
|
-
quiet_hours_enabled: result.rows[0].quiet_hours_enabled,
|
|
138
|
-
quiet_hours_start: result.rows[0].quiet_hours_start,
|
|
139
|
-
quiet_hours_end: result.rows[0].quiet_hours_end,
|
|
140
|
-
quiet_hours_timezone: result.rows[0].quiet_hours_timezone,
|
|
141
|
-
slack_channel: result.rows[0].slack_channel,
|
|
142
|
-
slack_dm_enabled: result.rows[0].slack_dm_enabled
|
|
143
|
-
},
|
|
144
|
-
updated_at: result.rows[0].updated_at
|
|
145
|
-
},
|
|
146
|
-
'Preferences updated',
|
|
147
|
-
{ Request_ID, Timestamp: new Date().toISOString() }
|
|
148
|
-
);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Validate and sanitize preferences input
|
|
153
|
-
*/
|
|
154
|
-
function validatePreferences(body) {
|
|
155
|
-
const result = {};
|
|
156
|
-
|
|
157
|
-
// Validate boolean fields
|
|
158
|
-
if (body.email_enabled !== undefined) {
|
|
159
|
-
if (typeof body.email_enabled !== 'boolean') {
|
|
160
|
-
return { error: 'email_enabled must be a boolean' };
|
|
161
|
-
}
|
|
162
|
-
result.email_enabled = body.email_enabled;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (body.slack_enabled !== undefined) {
|
|
166
|
-
if (typeof body.slack_enabled !== 'boolean') {
|
|
167
|
-
return { error: 'slack_enabled must be a boolean' };
|
|
168
|
-
}
|
|
169
|
-
result.slack_enabled = body.slack_enabled;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (body.quiet_hours_enabled !== undefined) {
|
|
173
|
-
if (typeof body.quiet_hours_enabled !== 'boolean') {
|
|
174
|
-
return { error: 'quiet_hours_enabled must be a boolean' };
|
|
175
|
-
}
|
|
176
|
-
result.quiet_hours_enabled = body.quiet_hours_enabled;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (body.slack_dm_enabled !== undefined) {
|
|
180
|
-
if (typeof body.slack_dm_enabled !== 'boolean') {
|
|
181
|
-
return { error: 'slack_dm_enabled must be a boolean' };
|
|
182
|
-
}
|
|
183
|
-
result.slack_dm_enabled = body.slack_dm_enabled;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Validate notification_types
|
|
187
|
-
if (body.notification_types !== undefined) {
|
|
188
|
-
if (typeof body.notification_types !== 'object') {
|
|
189
|
-
return { error: 'notification_types must be an object' };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const validTypes = ['pattern_promotion', 'weekly_digest', 'critical_violation', 'team_alert', 'curation_candidate'];
|
|
193
|
-
for (const type of Object.keys(body.notification_types)) {
|
|
194
|
-
if (!validTypes.includes(type)) {
|
|
195
|
-
return { error: `Invalid notification type: ${type}` };
|
|
196
|
-
}
|
|
197
|
-
const typePrefs = body.notification_types[type];
|
|
198
|
-
if (typeof typePrefs !== 'object') {
|
|
199
|
-
return { error: `notification_types.${type} must be an object` };
|
|
200
|
-
}
|
|
201
|
-
if (typePrefs.email !== undefined && typeof typePrefs.email !== 'boolean') {
|
|
202
|
-
return { error: `notification_types.${type}.email must be a boolean` };
|
|
203
|
-
}
|
|
204
|
-
if (typePrefs.slack !== undefined && typeof typePrefs.slack !== 'boolean') {
|
|
205
|
-
return { error: `notification_types.${type}.slack must be a boolean` };
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
result.notification_types = body.notification_types;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Validate project_overrides
|
|
212
|
-
if (body.project_overrides !== undefined) {
|
|
213
|
-
if (typeof body.project_overrides !== 'object') {
|
|
214
|
-
return { error: 'project_overrides must be an object' };
|
|
215
|
-
}
|
|
216
|
-
result.project_overrides = body.project_overrides;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Validate digest_frequency
|
|
220
|
-
if (body.digest_frequency !== undefined) {
|
|
221
|
-
const validFrequencies = ['daily', 'weekly', 'monthly', 'never'];
|
|
222
|
-
if (!validFrequencies.includes(body.digest_frequency)) {
|
|
223
|
-
return { error: `Invalid digest_frequency. Valid values: ${validFrequencies.join(', ')}` };
|
|
224
|
-
}
|
|
225
|
-
result.digest_frequency = body.digest_frequency;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Validate digest_day
|
|
229
|
-
if (body.digest_day !== undefined) {
|
|
230
|
-
if (typeof body.digest_day !== 'number' || body.digest_day < 0 || body.digest_day > 31) {
|
|
231
|
-
return { error: 'digest_day must be a number between 0 and 31' };
|
|
232
|
-
}
|
|
233
|
-
result.digest_day = body.digest_day;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Validate quiet hours
|
|
237
|
-
if (body.quiet_hours_start !== undefined) {
|
|
238
|
-
if (!/^\d{2}:\d{2}$/.test(body.quiet_hours_start)) {
|
|
239
|
-
return { error: 'quiet_hours_start must be in HH:MM format' };
|
|
240
|
-
}
|
|
241
|
-
result.quiet_hours_start = body.quiet_hours_start;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (body.quiet_hours_end !== undefined) {
|
|
245
|
-
if (!/^\d{2}:\d{2}$/.test(body.quiet_hours_end)) {
|
|
246
|
-
return { error: 'quiet_hours_end must be in HH:MM format' };
|
|
247
|
-
}
|
|
248
|
-
result.quiet_hours_end = body.quiet_hours_end;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (body.quiet_hours_timezone !== undefined) {
|
|
252
|
-
result.quiet_hours_timezone = body.quiet_hours_timezone;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Validate slack_channel
|
|
256
|
-
if (body.slack_channel !== undefined) {
|
|
257
|
-
if (body.slack_channel !== null && typeof body.slack_channel !== 'string') {
|
|
258
|
-
return { error: 'slack_channel must be a string or null' };
|
|
259
|
-
}
|
|
260
|
-
result.slack_channel = body.slack_channel;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return result;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Update digest queue when frequency changes
|
|
268
|
-
*/
|
|
269
|
-
async function updateDigestQueue(email, frequency, digestDay) {
|
|
270
|
-
// Remove existing pending digests
|
|
271
|
-
await executeQuery(`
|
|
272
|
-
DELETE FROM rapport.digest_queue
|
|
273
|
-
WHERE email_address = $1 AND status = 'pending'
|
|
274
|
-
`, [email]);
|
|
275
|
-
|
|
276
|
-
// If frequency is 'never', don't schedule anything
|
|
277
|
-
if (frequency === 'never') {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Calculate next scheduled time
|
|
282
|
-
const now = new Date();
|
|
283
|
-
let scheduledFor;
|
|
284
|
-
|
|
285
|
-
switch (frequency) {
|
|
286
|
-
case 'daily':
|
|
287
|
-
scheduledFor = new Date(now);
|
|
288
|
-
scheduledFor.setDate(scheduledFor.getDate() + 1);
|
|
289
|
-
scheduledFor.setHours(9, 0, 0, 0); // 9 AM next day
|
|
290
|
-
break;
|
|
291
|
-
|
|
292
|
-
case 'weekly':
|
|
293
|
-
scheduledFor = new Date(now);
|
|
294
|
-
const daysUntilNextDigestDay = (digestDay - now.getDay() + 7) % 7 || 7;
|
|
295
|
-
scheduledFor.setDate(scheduledFor.getDate() + daysUntilNextDigestDay);
|
|
296
|
-
scheduledFor.setHours(9, 0, 0, 0); // 9 AM
|
|
297
|
-
break;
|
|
298
|
-
|
|
299
|
-
case 'monthly':
|
|
300
|
-
scheduledFor = new Date(now);
|
|
301
|
-
scheduledFor.setMonth(scheduledFor.getMonth() + 1);
|
|
302
|
-
scheduledFor.setDate(digestDay || 1);
|
|
303
|
-
scheduledFor.setHours(9, 0, 0, 0); // 9 AM
|
|
304
|
-
break;
|
|
305
|
-
|
|
306
|
-
default:
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Insert new digest into queue
|
|
311
|
-
await executeQuery(`
|
|
312
|
-
INSERT INTO rapport.digest_queue (email_address, digest_type, scheduled_for, status, digest_data)
|
|
313
|
-
VALUES ($1, $2, $3, 'pending', '{}'::jsonb)
|
|
314
|
-
ON CONFLICT (email_address, digest_type, scheduled_for) DO NOTHING
|
|
315
|
-
`, [email, frequency, scheduledFor.toISOString()]);
|
|
316
|
-
}
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pattern Evaluate Promotion Handler
|
|
3
|
-
* Checks if a pattern meets promotion thresholds for becoming a standard
|
|
4
|
-
*
|
|
5
|
-
* POST /api/patterns/evaluate-promotion
|
|
6
|
-
* Body: { pattern, project_id, evaluate_only }
|
|
7
|
-
*
|
|
8
|
-
* Called by: pre-compact.js hook (evaluateForPromotion method)
|
|
9
|
-
*
|
|
10
|
-
* Promotion Criteria:
|
|
11
|
-
* - handoff_count >= 10 (used 10+ times)
|
|
12
|
-
* - success_rate >= 0.70 (70% success)
|
|
13
|
-
* - developer_count >= 3 (used by 3+ developers)
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Evaluate pattern for promotion to standard
|
|
20
|
-
*/
|
|
21
|
-
async function evaluatePatternPromotion({ body: requestBody = {}, requestContext }) {
|
|
22
|
-
try {
|
|
23
|
-
const Request_ID = requestContext?.requestId || 'unknown';
|
|
24
|
-
|
|
25
|
-
const {
|
|
26
|
-
pattern,
|
|
27
|
-
project_id,
|
|
28
|
-
evaluate_only = true
|
|
29
|
-
} = requestBody;
|
|
30
|
-
|
|
31
|
-
// Validate required fields
|
|
32
|
-
if (!pattern || !pattern.element) {
|
|
33
|
-
return createErrorResponse(400, 'pattern.element is required');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Generate pattern_id from element
|
|
37
|
-
const patternId = pattern.pattern_id || `pat_${pattern.element.toLowerCase().replace(/\s+/g, '_').substring(0, 50)}`;
|
|
38
|
-
|
|
39
|
-
// Look up pattern in rapport.patterns
|
|
40
|
-
const patternQuery = `
|
|
41
|
-
SELECT
|
|
42
|
-
p.pattern_id,
|
|
43
|
-
p.intent,
|
|
44
|
-
p.maturity,
|
|
45
|
-
p.handoff_count,
|
|
46
|
-
p.successful_handoffs,
|
|
47
|
-
p.failed_handoffs,
|
|
48
|
-
p.discovered_at,
|
|
49
|
-
p.last_used
|
|
50
|
-
FROM rapport.patterns p
|
|
51
|
-
WHERE p.pattern_id = $1
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
const patternResult = await executeQuery(patternQuery, [patternId]);
|
|
55
|
-
|
|
56
|
-
if (patternResult.rows.length === 0) {
|
|
57
|
-
return createSuccessResponse(
|
|
58
|
-
{
|
|
59
|
-
Records: [{
|
|
60
|
-
pattern_id: patternId,
|
|
61
|
-
eligible: false,
|
|
62
|
-
reason: 'not_found',
|
|
63
|
-
message: 'Pattern not found in patterns table'
|
|
64
|
-
}]
|
|
65
|
-
},
|
|
66
|
-
'Pattern not found',
|
|
67
|
-
{ Total_Records: 0, Request_ID, Timestamp: new Date().toISOString() }
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const pat = patternResult.rows[0];
|
|
72
|
-
|
|
73
|
-
// Query usage metrics
|
|
74
|
-
const metricsQuery = `
|
|
75
|
-
SELECT
|
|
76
|
-
COUNT(DISTINCT pu.email_address) as developer_count,
|
|
77
|
-
COUNT(DISTINCT pu.session_id) as session_count,
|
|
78
|
-
COUNT(*) FILTER (WHERE pu.success = true) as successes,
|
|
79
|
-
COUNT(*) as total_uses
|
|
80
|
-
FROM rapport.pattern_usage pu
|
|
81
|
-
WHERE pu.pattern_id = $1
|
|
82
|
-
`;
|
|
83
|
-
|
|
84
|
-
const metricsResult = await executeQuery(metricsQuery, [patternId]);
|
|
85
|
-
const metrics = metricsResult.rows[0];
|
|
86
|
-
|
|
87
|
-
const handoffCount = parseInt(pat.handoff_count) || 0;
|
|
88
|
-
const successRate = handoffCount > 0
|
|
89
|
-
? (parseInt(pat.successful_handoffs) || 0) / handoffCount
|
|
90
|
-
: 0;
|
|
91
|
-
const developerCount = parseInt(metrics.developer_count) || 0;
|
|
92
|
-
const sessionCount = parseInt(metrics.session_count) || 0;
|
|
93
|
-
|
|
94
|
-
// Check promotion thresholds
|
|
95
|
-
const eligible =
|
|
96
|
-
handoffCount >= 10 &&
|
|
97
|
-
successRate >= 0.70 &&
|
|
98
|
-
developerCount >= 3;
|
|
99
|
-
|
|
100
|
-
const response = {
|
|
101
|
-
pattern_id: patternId,
|
|
102
|
-
eligible: eligible,
|
|
103
|
-
metrics: {
|
|
104
|
-
handoff_count: handoffCount,
|
|
105
|
-
success_rate: parseFloat(successRate.toFixed(3)),
|
|
106
|
-
developer_count: developerCount,
|
|
107
|
-
session_count: sessionCount,
|
|
108
|
-
maturity: pat.maturity,
|
|
109
|
-
success_correlation: parseFloat(successRate.toFixed(3))
|
|
110
|
-
},
|
|
111
|
-
thresholds: {
|
|
112
|
-
min_handoffs: 10,
|
|
113
|
-
min_success_rate: 0.70,
|
|
114
|
-
min_developers: 3
|
|
115
|
-
},
|
|
116
|
-
proposed_category: pattern.category || null
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// If eligible and not evaluate_only, create curation candidate
|
|
120
|
-
if (eligible && !evaluate_only) {
|
|
121
|
-
const candidateQuery = `
|
|
122
|
-
INSERT INTO rapport.curation_candidates (
|
|
123
|
-
pattern_id,
|
|
124
|
-
proposed_category,
|
|
125
|
-
evidence,
|
|
126
|
-
status,
|
|
127
|
-
created_at
|
|
128
|
-
) VALUES ($1, $2, $3, 'pending', NOW())
|
|
129
|
-
ON CONFLICT (pattern_id) WHERE status = 'pending'
|
|
130
|
-
DO UPDATE SET
|
|
131
|
-
evidence = EXCLUDED.evidence,
|
|
132
|
-
created_at = NOW()
|
|
133
|
-
RETURNING candidate_id
|
|
134
|
-
`;
|
|
135
|
-
|
|
136
|
-
const evidence = {
|
|
137
|
-
handoff_count: handoffCount,
|
|
138
|
-
success_rate: successRate,
|
|
139
|
-
developer_count: developerCount,
|
|
140
|
-
session_count: sessionCount,
|
|
141
|
-
maturity: pat.maturity,
|
|
142
|
-
evaluated_at: new Date().toISOString()
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
const candidateResult = await executeQuery(candidateQuery, [
|
|
147
|
-
patternId,
|
|
148
|
-
pattern.category || 'uncategorized',
|
|
149
|
-
JSON.stringify(evidence)
|
|
150
|
-
]);
|
|
151
|
-
|
|
152
|
-
if (candidateResult.rows.length > 0) {
|
|
153
|
-
response.candidate_id = candidateResult.rows[0].candidate_id;
|
|
154
|
-
}
|
|
155
|
-
} catch (candidateError) {
|
|
156
|
-
// Candidate creation failed — still return eligibility result
|
|
157
|
-
console.error('[patternEvaluatePromotionPost] Candidate creation failed:', candidateError.message);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return createSuccessResponse(
|
|
162
|
-
{ Records: [response] },
|
|
163
|
-
eligible ? 'Pattern eligible for promotion' : 'Pattern does not meet promotion criteria',
|
|
164
|
-
{ Total_Records: 1, Request_ID, Timestamp: new Date().toISOString() }
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
} catch (error) {
|
|
168
|
-
console.error('Handler Error:', error);
|
|
169
|
-
return handleError(error);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
exports.handler = wrapHandler(evaluatePatternPromotion);
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pattern Usage Handler
|
|
3
|
-
* Records pattern usage (success/failure) from Claude Code sessions
|
|
4
|
-
*
|
|
5
|
-
* POST /api/patterns/usage
|
|
6
|
-
* Body: { pattern, session_id, user_id, project_id, success, context, timestamp }
|
|
7
|
-
*
|
|
8
|
-
* Called by: pre-compact.js hook (reinforcePattern method)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Record pattern usage
|
|
15
|
-
* Updates usage count and maturity level based on success
|
|
16
|
-
*/
|
|
17
|
-
async function recordPatternUsage({ body: requestBody = {}, requestContext }) {
|
|
18
|
-
try {
|
|
19
|
-
const Request_ID = requestContext?.requestId || 'unknown';
|
|
20
|
-
|
|
21
|
-
const {
|
|
22
|
-
pattern,
|
|
23
|
-
session_id,
|
|
24
|
-
user_id,
|
|
25
|
-
project_id,
|
|
26
|
-
success = true,
|
|
27
|
-
context = {},
|
|
28
|
-
timestamp
|
|
29
|
-
} = requestBody;
|
|
30
|
-
|
|
31
|
-
// Validate required fields
|
|
32
|
-
if (!pattern || !pattern.element) {
|
|
33
|
-
return createErrorResponse(400, 'pattern.element is required');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!session_id) {
|
|
37
|
-
return createErrorResponse(400, 'session_id is required');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Generate pattern_id from element if not provided
|
|
41
|
-
const patternId = pattern.pattern_id || `pat_${pattern.element.toLowerCase().replace(/\s+/g, '_').substring(0, 50)}`;
|
|
42
|
-
|
|
43
|
-
// If no project_id, we can't create a pattern record (FK constraint)
|
|
44
|
-
// Return success anyway - hooks should not fail
|
|
45
|
-
if (!project_id) {
|
|
46
|
-
console.log('[patternUsagePost] No project_id provided, skipping pattern record');
|
|
47
|
-
return createSuccessResponse(
|
|
48
|
-
{
|
|
49
|
-
Records: [{
|
|
50
|
-
pattern_id: patternId,
|
|
51
|
-
recorded: false,
|
|
52
|
-
reason: 'no_project_id',
|
|
53
|
-
message: 'Pattern usage logged but not recorded (no project context)'
|
|
54
|
-
}]
|
|
55
|
-
},
|
|
56
|
-
'Pattern usage acknowledged (no project context)',
|
|
57
|
-
{
|
|
58
|
-
Total_Records: 0,
|
|
59
|
-
Request_ID,
|
|
60
|
-
Timestamp: new Date().toISOString()
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// First, ensure the pattern exists in rapport.patterns (upsert)
|
|
66
|
-
const upsertPatternQuery = `
|
|
67
|
-
INSERT INTO rapport.patterns (
|
|
68
|
-
pattern_id,
|
|
69
|
-
project_id,
|
|
70
|
-
intent,
|
|
71
|
-
constraints,
|
|
72
|
-
outcome_criteria,
|
|
73
|
-
maturity,
|
|
74
|
-
handoff_count,
|
|
75
|
-
successful_handoffs,
|
|
76
|
-
failed_handoffs,
|
|
77
|
-
discovered_by,
|
|
78
|
-
discovered_at,
|
|
79
|
-
last_used,
|
|
80
|
-
pattern_data
|
|
81
|
-
) VALUES (
|
|
82
|
-
$1, $2, $3, $4, $5, 'provisional', 1,
|
|
83
|
-
CASE WHEN $6 THEN 1 ELSE 0 END,
|
|
84
|
-
CASE WHEN $6 THEN 0 ELSE 1 END,
|
|
85
|
-
$7, NOW(), NOW(), $8
|
|
86
|
-
)
|
|
87
|
-
ON CONFLICT (pattern_id) DO UPDATE SET
|
|
88
|
-
handoff_count = rapport.patterns.handoff_count + 1,
|
|
89
|
-
successful_handoffs = rapport.patterns.successful_handoffs + CASE WHEN $6 THEN 1 ELSE 0 END,
|
|
90
|
-
failed_handoffs = rapport.patterns.failed_handoffs + CASE WHEN $6 THEN 0 ELSE 1 END,
|
|
91
|
-
last_used = NOW(),
|
|
92
|
-
maturity = CASE
|
|
93
|
-
WHEN rapport.patterns.handoff_count >= 9 AND
|
|
94
|
-
(rapport.patterns.successful_handoffs + CASE WHEN $6 THEN 1 ELSE 0 END)::float /
|
|
95
|
-
(rapport.patterns.handoff_count + 1) >= 0.7
|
|
96
|
-
THEN 'reinforced'
|
|
97
|
-
WHEN rapport.patterns.handoff_count >= 4 AND
|
|
98
|
-
(rapport.patterns.successful_handoffs + CASE WHEN $6 THEN 1 ELSE 0 END)::float /
|
|
99
|
-
(rapport.patterns.handoff_count + 1) >= 0.6
|
|
100
|
-
THEN 'validated'
|
|
101
|
-
ELSE rapport.patterns.maturity
|
|
102
|
-
END
|
|
103
|
-
RETURNING pattern_id, maturity, handoff_count, successful_handoffs, failed_handoffs
|
|
104
|
-
`;
|
|
105
|
-
|
|
106
|
-
const patternResult = await executeQuery(upsertPatternQuery, [
|
|
107
|
-
patternId,
|
|
108
|
-
project_id || null,
|
|
109
|
-
pattern.intent || pattern.element,
|
|
110
|
-
JSON.stringify(pattern.constraints || []),
|
|
111
|
-
JSON.stringify(pattern.outcome_criteria || []),
|
|
112
|
-
success,
|
|
113
|
-
user_id || 'anonymous',
|
|
114
|
-
JSON.stringify({
|
|
115
|
-
type: pattern.type,
|
|
116
|
-
category: pattern.category,
|
|
117
|
-
file: pattern.file,
|
|
118
|
-
confidence: pattern.confidence,
|
|
119
|
-
evidence: pattern.evidence
|
|
120
|
-
})
|
|
121
|
-
]);
|
|
122
|
-
|
|
123
|
-
const updatedPattern = patternResult.rows[0];
|
|
124
|
-
|
|
125
|
-
// Record the individual usage in pattern_usage table
|
|
126
|
-
const usageQuery = `
|
|
127
|
-
INSERT INTO rapport.pattern_usage (
|
|
128
|
-
pattern_id,
|
|
129
|
-
email_address,
|
|
130
|
-
session_id,
|
|
131
|
-
success,
|
|
132
|
-
context,
|
|
133
|
-
used_at
|
|
134
|
-
) VALUES (
|
|
135
|
-
$1, $2, $3, $4, $5, $6
|
|
136
|
-
)
|
|
137
|
-
RETURNING usage_id
|
|
138
|
-
`;
|
|
139
|
-
|
|
140
|
-
const usageResult = await executeQuery(usageQuery, [
|
|
141
|
-
patternId,
|
|
142
|
-
user_id || 'anonymous',
|
|
143
|
-
session_id,
|
|
144
|
-
success,
|
|
145
|
-
JSON.stringify({
|
|
146
|
-
working_directory: context.working_directory,
|
|
147
|
-
source: context.source,
|
|
148
|
-
file: pattern.file,
|
|
149
|
-
category: pattern.category
|
|
150
|
-
}),
|
|
151
|
-
timestamp ? new Date(timestamp) : new Date()
|
|
152
|
-
]);
|
|
153
|
-
|
|
154
|
-
// Calculate success rate
|
|
155
|
-
const successRate = updatedPattern.successful_handoffs / updatedPattern.handoff_count;
|
|
156
|
-
|
|
157
|
-
return createSuccessResponse(
|
|
158
|
-
{
|
|
159
|
-
Records: [{
|
|
160
|
-
usage_id: usageResult.rows[0]?.usage_id,
|
|
161
|
-
pattern_id: updatedPattern.pattern_id,
|
|
162
|
-
maturity: updatedPattern.maturity,
|
|
163
|
-
usage_count: updatedPattern.handoff_count,
|
|
164
|
-
success_rate: successRate.toFixed(2),
|
|
165
|
-
recorded: true
|
|
166
|
-
}]
|
|
167
|
-
},
|
|
168
|
-
success ? 'Pattern usage recorded (success)' : 'Pattern usage recorded (failure)',
|
|
169
|
-
{
|
|
170
|
-
Total_Records: 1,
|
|
171
|
-
Request_ID,
|
|
172
|
-
Timestamp: new Date().toISOString()
|
|
173
|
-
}
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
} catch (error) {
|
|
177
|
-
console.error('Handler Error:', error);
|
|
178
|
-
return handleError(error);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
exports.handler = wrapHandler(recordPatternUsage);
|