@equilateral_ai/mindmeld 3.5.3 → 4.0.1

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