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