@equilateral_ai/mindmeld 3.5.3 → 4.0.2

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