@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,405 +0,0 @@
1
- /**
2
- * Standards Relevant Post Handler
3
- *
4
- * Returns top 10 relevant standards for a project based on detected characteristics.
5
- * The session-start hook calls this endpoint with locally-detected project characteristics;
6
- * the handler maps them to categories, queries the database, ranks results, and returns
7
- * the most relevant standards for injection into the AI coding session.
8
- *
9
- * POST /api/standards/relevant
10
- * Auth: Cognito JWT required
11
- * Body: { characteristics, projectId?, preferences? }
12
- */
13
-
14
- const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, getPredictedStandards, logStandardsActivation, createFrame } = require('./helpers');
15
-
16
- /**
17
- * Category weights for relevance scoring
18
- */
19
- const CATEGORY_WEIGHTS = {
20
- // Code standard categories
21
- 'serverless-saas-aws': 1.0,
22
- 'frontend-development': 1.0,
23
- 'database': 0.9,
24
- 'backend': 0.9,
25
- 'compliance-security': 0.9,
26
- 'deployment': 0.8,
27
- 'testing': 0.7,
28
- 'real-time-systems': 0.7,
29
- 'well-architected': 0.7,
30
- 'cost-optimization': 0.7,
31
- 'multi-agent-orchestration': 0.1, // Infrastructure config, rarely needed in coding sessions
32
- // Business domains — moderate weights, boosted by recency when relevant
33
- 'ip-strategy': 0.6,
34
- 'architecture-decisions': 0.8,
35
- 'go-to-market': 0.6,
36
- 'operations': 0.5,
37
- 'legal-process': 0.5,
38
- 'finance': 0.5,
39
- 'communication': 0.4,
40
- 'product-strategy': 0.6,
41
- 'investor-relations': 0.4,
42
- };
43
-
44
- /**
45
- * Map project characteristics to relevant standard categories
46
- */
47
- function mapCharacteristicsToCategories(characteristics) {
48
- const categories = new Set();
49
-
50
- if (characteristics.hasLambda || characteristics.hasSAM) {
51
- categories.add('serverless-saas-aws');
52
- categories.add('cost-optimization');
53
- }
54
- if (characteristics.hasReact) {
55
- categories.add('frontend-development');
56
- }
57
- if (characteristics.hasDatabase) {
58
- categories.add('database');
59
- }
60
- if (characteristics.hasMultiAgent) {
61
- categories.add('multi-agent-orchestration');
62
- }
63
- if (characteristics.hasAPI) {
64
- categories.add('backend');
65
- }
66
- if (characteristics.hasTests) {
67
- categories.add('testing');
68
- }
69
- if (characteristics.hasSAM || characteristics.hasLambda || characteristics.hasAPI) {
70
- categories.add('deployment');
71
- }
72
-
73
- // Always relevant
74
- categories.add('compliance-security');
75
- categories.add('well-architected');
76
-
77
- return Array.from(categories);
78
- }
79
-
80
- /**
81
- * Rank standards by relevance score
82
- */
83
- function rankStandards(standards, recentCategories) {
84
- return standards.map(standard => {
85
- let score = 0;
86
-
87
- // Base score from correlation
88
- score += (standard.correlation || 1.0) * 40;
89
-
90
- // Maturity score (includes business invariant lifecycle values)
91
- const maturityScores = { enforced: 30, reinforced: 25, validated: 20, solidified: 15, recommended: 10, provisional: 5 };
92
- score += maturityScores[standard.maturity] || 0;
93
-
94
- // Category weight
95
- const categoryWeight = CATEGORY_WEIGHTS[standard.category] || 0.5;
96
- score += categoryWeight * 20;
97
-
98
- // File applicability bonus
99
- if (standard.applicable_files && standard.applicable_files.length > 0) {
100
- score += 5;
101
- }
102
-
103
- // Cost impact bonus (critical patterns)
104
- if (standard.cost_impact && standard.cost_impact.severity === 'critical') {
105
- score += 10;
106
- }
107
-
108
- // Anti-patterns bonus (important to know what NOT to do)
109
- if (standard.anti_patterns) {
110
- const apCount = Array.isArray(standard.anti_patterns)
111
- ? standard.anti_patterns.length
112
- : Object.keys(standard.anti_patterns).length;
113
- if (apCount > 0) score += 5;
114
- }
115
-
116
- // Workflow bonus — workflows are high-value procedural knowledge
117
- const isWorkflow = (standard.rule && standard.rule.startsWith('WORKFLOW:'))
118
- || (Array.isArray(standard.keywords) && standard.keywords.includes('workflow'));
119
- if (isWorkflow) score += 10;
120
-
121
- // Rationale bonus — business invariants with documented reasoning
122
- if (standard.rationale) score += 5;
123
-
124
- // Recency bonus — boost categories the user has been working in recently
125
- // Scaled by category weight to prevent feedback loops (low-weight categories
126
- // that got injected shouldn't bootstrap themselves back into the top 10)
127
- if (recentCategories && recentCategories[standard.category]) {
128
- const usageCount = recentCategories[standard.category];
129
- let rawBonus;
130
- if (usageCount >= 8) rawBonus = 25;
131
- else if (usageCount >= 4) rawBonus = 18;
132
- else rawBonus = 10;
133
- score += rawBonus * categoryWeight;
134
- }
135
-
136
- // Recency decay: patterns not seen in 30+ days get penalized
137
- if (standard.last_seen_at) {
138
- const daysSinceLastSeen = (Date.now() - new Date(standard.last_seen_at).getTime()) / (1000 * 60 * 60 * 24);
139
- if (daysSinceLastSeen > 30) {
140
- score *= 0.5;
141
- }
142
- }
143
-
144
- // Frequency boost: high-occurrence patterns are more valuable
145
- const occurrenceCount = standard.occurrence_count || 1;
146
- if (occurrenceCount > 50) {
147
- score *= 1.5;
148
- } else if (occurrenceCount > 10) {
149
- score *= 1.2;
150
- }
151
-
152
- return {
153
- ...standard,
154
- relevance_score: Math.round(score * 10) / 10
155
- };
156
- }).sort((a, b) => {
157
- // Load-bearing standards always sort to the top
158
- const aLB = a.load_bearing ? 1 : 0;
159
- const bLB = b.load_bearing ? 1 : 0;
160
- if (bLB !== aLB) return bLB - aLB;
161
- return b.relevance_score - a.relevance_score;
162
- });
163
- }
164
-
165
- /**
166
- * Apply user preferences to filter standards
167
- */
168
- function applyPreferences(standards, preferences) {
169
- if (!preferences) return standards;
170
-
171
- const enabledCategories = preferences.enabled_categories || {};
172
- const standardOverrides = preferences.standard_overrides || {};
173
-
174
- return standards.filter(standard => {
175
- const category = standard.category;
176
- const standardPath = `${category}/${standard.element}`;
177
-
178
- // Individual override takes priority
179
- if (standardPath in standardOverrides) {
180
- return standardOverrides[standardPath] === true;
181
- }
182
-
183
- // Category-level setting
184
- if (category in enabledCategories) {
185
- return enabledCategories[category] === true;
186
- }
187
-
188
- // Default: include
189
- return true;
190
- });
191
- }
192
-
193
- async function getRelevantStandards({ body, requestContext }) {
194
- const email = requestContext.authorizer?.claims?.email
195
- || requestContext.authorizer?.jwt?.claims?.email;
196
-
197
- if (!email) {
198
- return createErrorResponse(401, 'Authentication required');
199
- }
200
-
201
- // Verify active subscription and resolve company_id for tenant isolation
202
- const subResult = await executeQuery(`
203
- SELECT c.subscription_tier, c.subscription_status, ue.company_id
204
- FROM rapport.users u
205
- JOIN rapport.clients c ON u.client_id = c.client_id
206
- LEFT JOIN rapport.user_entitlements ue ON u.email_address = ue.email_address
207
- WHERE u.email_address = $1
208
- LIMIT 1
209
- `, [email]);
210
-
211
- let companyId = null;
212
- if (subResult.rows.length > 0) {
213
- const { subscription_tier, subscription_status } = subResult.rows[0];
214
- companyId = subResult.rows[0].company_id;
215
- if (!subscription_tier || subscription_tier === 'free') {
216
- return createErrorResponse(403, 'Active MindMeld subscription required. Subscribe at app.mindmeld.dev');
217
- }
218
- if (subscription_status === 'canceled') {
219
- return createErrorResponse(403, 'Subscription canceled. Resubscribe at app.mindmeld.dev');
220
- }
221
- }
222
-
223
- // Parse request body
224
- let requestBody;
225
- try {
226
- requestBody = typeof body === 'string' ? JSON.parse(body) : body;
227
- } catch (e) {
228
- return createErrorResponse(400, 'Invalid request body');
229
- }
230
-
231
- const { characteristics, preferences } = requestBody || {};
232
-
233
- if (!characteristics || typeof characteristics !== 'object') {
234
- return createErrorResponse(400, 'characteristics object is required');
235
- }
236
-
237
- // Map characteristics to categories
238
- const categories = mapCharacteristicsToCategories(characteristics);
239
-
240
- if (categories.length === 0) {
241
- return createSuccessResponse({
242
- standards: [],
243
- categories: [],
244
- total_matched: 0
245
- }, 'No relevant categories detected');
246
- }
247
-
248
- // Query recent session categories first (fast), then merge into categories for main query
249
- const recentCategories = {};
250
- try {
251
- const recencyResult = await executeQuery(`
252
- SELECT sp.category, COUNT(*) as usage_count
253
- FROM rapport.session_standards ss
254
- JOIN rapport.sessions s ON s.session_id = ss.session_id
255
- JOIN rapport.standards_patterns sp ON sp.pattern_id = ss.standard_id
256
- WHERE s.email_address = $1
257
- AND s.started_at >= NOW() - INTERVAL '7 days'
258
- GROUP BY sp.category
259
- ORDER BY usage_count DESC
260
- LIMIT 5
261
- `, [email]);
262
- for (const row of recencyResult.rows) {
263
- recentCategories[row.category] = parseInt(row.usage_count, 10);
264
- }
265
- } catch (err) {
266
- console.error('[standardsRelevant] Recency query failed:', err.message);
267
- }
268
-
269
- // Merge recency categories into query — recent activity should always be represented
270
- for (const category of Object.keys(recentCategories)) {
271
- if (!categories.includes(category)) {
272
- categories.push(category);
273
- }
274
- }
275
-
276
- // Query standards — tenant-isolated via get_effective_standards()
277
- const maturityList = ['enforced', 'validated', 'recommended', 'provisional', 'solidified', 'reinforced'];
278
- const result = await executeQuery(`
279
- SELECT * FROM rapport.get_effective_standards($1, $2::varchar[], $3::varchar[])
280
- ORDER BY
281
- CASE
282
- WHEN maturity = 'enforced' THEN 1
283
- WHEN maturity = 'reinforced' THEN 2
284
- WHEN maturity = 'validated' THEN 3
285
- WHEN maturity = 'solidified' THEN 4
286
- WHEN maturity = 'recommended' THEN 5
287
- ELSE 6
288
- END,
289
- correlation DESC
290
- `, [companyId, categories, maturityList]);
291
-
292
- // Predictive cache: fetch standards with >80% activation rate for this project
293
- let predictedStandards = [];
294
- const projectId = requestBody.projectId;
295
- if (projectId) {
296
- try {
297
- const predicted = await getPredictedStandards(projectId);
298
- if (predicted.length > 0) {
299
- const predictedIds = new Set(predicted.map(p => p.standard_id));
300
- // Pull predicted standards from query results and mark them as pre-cached
301
- predictedStandards = result.rows
302
- .filter(s => predictedIds.has(s.pattern_id || s.standard_id))
303
- .map(s => ({ ...s, relevance_score: 100, predicted: true }));
304
- }
305
- } catch (err) {
306
- console.error('[standardsRelevant] Predictive cache lookup failed:', err.message);
307
- }
308
- }
309
-
310
- // Rank by relevance with recency boost
311
- let ranked = rankStandards(result.rows, recentCategories);
312
-
313
- // Deduplicate by element name (same rule can be ingested with different path prefixes)
314
- const seenElements = new Set();
315
- ranked = ranked.filter(standard => {
316
- const key = standard.element;
317
- if (seenElements.has(key)) return false;
318
- seenElements.add(key);
319
- return true;
320
- });
321
-
322
- // Apply preferences if provided
323
- if (preferences) {
324
- ranked = applyPreferences(ranked, preferences);
325
- }
326
-
327
- // Return top 10 with diversity caps:
328
- // - Max 2 per category (prevents single-category saturation)
329
- // - Max 1 per standard title (prevents same-file rule pairs wasting slots)
330
- // Predicted standards are included first (they skip scoring)
331
- const MAX_PER_CATEGORY = 2;
332
- const MAX_PER_TITLE = 1;
333
- const top = [];
334
- const categoryCounts = {};
335
- const titleCounts = {};
336
- const seenPredicted = new Set();
337
-
338
- // Add predicted standards first — they have proven relevance
339
- for (const standard of predictedStandards) {
340
- const cat = standard.category;
341
- const title = standard.title || standard.element;
342
- const key = standard.element;
343
- categoryCounts[cat] = (categoryCounts[cat] || 0) + 1;
344
- titleCounts[title] = (titleCounts[title] || 0) + 1;
345
- seenPredicted.add(key);
346
- if (categoryCounts[cat] <= MAX_PER_CATEGORY && titleCounts[title] <= MAX_PER_TITLE) {
347
- top.push(standard);
348
- if (top.length >= 10) break;
349
- }
350
- }
351
-
352
- // Fill remaining slots from scored standards
353
- for (const standard of ranked) {
354
- if (top.length >= 10) break;
355
- if (seenPredicted.has(standard.element)) continue;
356
- const cat = standard.category;
357
- const title = standard.title || standard.element;
358
- categoryCounts[cat] = (categoryCounts[cat] || 0) + 1;
359
- titleCounts[title] = (titleCounts[title] || 0) + 1;
360
- if (categoryCounts[cat] <= MAX_PER_CATEGORY && titleCounts[title] <= MAX_PER_TITLE) {
361
- top.push(standard);
362
- }
363
- }
364
-
365
- // Log activated standards for future predictions
366
- if (projectId && top.length > 0) {
367
- const activatedIds = top
368
- .map(s => s.pattern_id || s.standard_id)
369
- .filter(id => id != null);
370
- logStandardsActivation(projectId, activatedIds).catch(err => {
371
- console.error('[standardsRelevant] Activation logging failed:', err.message);
372
- });
373
- }
374
-
375
- // Track decision frame for this standards selection
376
- let frameId = null;
377
- if (projectId && top.length > 0) {
378
- try {
379
- const standardIds = top
380
- .map(s => s.pattern_id || s.standard_id)
381
- .filter(id => id != null);
382
- const avgScore = top.reduce((sum, s) => sum + (s.relevance_score || 0), 0) / top.length;
383
- const confidence = Math.min(avgScore / 100, 1);
384
- const frame = await createFrame({
385
- projectId,
386
- sessionId: requestBody.sessionId || null,
387
- standardIds,
388
- confidence,
389
- context: { categories, characteristics, total_matched: result.rows.length }
390
- });
391
- frameId = frame.frame_id;
392
- } catch (err) {
393
- console.error('[standardsRelevant] Decision frame creation failed:', err.message);
394
- }
395
- }
396
-
397
- return createSuccessResponse({
398
- standards: top,
399
- categories,
400
- total_matched: result.rows.length,
401
- frame_id: frameId
402
- }, 'Relevant standards retrieved');
403
- }
404
-
405
- exports.handler = wrapHandler(getRelevantStandards);
@@ -1,161 +0,0 @@
1
- /**
2
- * Standards Transition Handler
3
- * Executes lifecycle state transitions on standards and discoveries
4
- *
5
- * POST /api/standards/transition
6
- * Body: { standard_id, action: 'approve'|'reject'|'observe'|'disable'|'deprecate'|'delete'|'enable'|'cancel_deprecation', reason?, reason_code? }
7
- * Auth: Cognito JWT required
8
- *
9
- * For discovery IDs (from onboarding_discoveries table):
10
- * approve → creates pattern + marks discovery approved
11
- * reject → marks discovery rejected
12
- * observe → marks discovery as observing (deferred review)
13
- */
14
-
15
- const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
16
- const { StandardLifecycle } = require('./core/StandardLifecycle');
17
-
18
- const lifecycle = new StandardLifecycle();
19
-
20
- const DISCOVERY_ACTIONS = ['approve', 'reject', 'observe'];
21
-
22
- /**
23
- * Handle transitions for onboarding discoveries
24
- */
25
- async function transitionDiscovery(discoveryId, action, email, reason, reasonCode) {
26
- const discovery = await executeQuery(`
27
- SELECT discovery_id, project_id, discovery_type, pattern_name, pattern_description, confidence, evidence, status
28
- FROM rapport.onboarding_discoveries
29
- WHERE discovery_id = $1
30
- `, [discoveryId]);
31
-
32
- if (discovery.rowCount === 0) {
33
- return null; // Not a discovery either
34
- }
35
-
36
- const row = discovery.rows[0];
37
- const oldStatus = row.status;
38
-
39
- if (action === 'approve') {
40
- // Create pattern from discovery
41
- const patternId = `pat_${row.project_id}_${Date.now()}`;
42
- await executeQuery(`
43
- INSERT INTO rapport.patterns (
44
- pattern_id, project_id, intent, constraints, outcome_criteria,
45
- maturity, discovered_by, pattern_data
46
- ) VALUES ($1, $2, $3, $4, $5, 'provisional', $6, $7)
47
- `, [
48
- patternId,
49
- row.project_id,
50
- row.pattern_name,
51
- JSON.stringify([row.pattern_description || '']),
52
- JSON.stringify([`Discovered via onboarding: ${row.discovery_type}`]),
53
- email,
54
- JSON.stringify({
55
- source: 'discovery_review',
56
- discovery_type: row.discovery_type,
57
- evidence: row.evidence
58
- })
59
- ]);
60
-
61
- await executeQuery(`
62
- UPDATE rapport.onboarding_discoveries
63
- SET status = 'approved', reviewed_at = NOW(), updated_at = NOW(), pattern_id = $1
64
- WHERE discovery_id = $2
65
- `, [patternId, discoveryId]);
66
-
67
- return { old_state: oldStatus, new_state: 'approved', pattern_id: patternId };
68
- }
69
-
70
- if (action === 'reject') {
71
- await executeQuery(`
72
- UPDATE rapport.onboarding_discoveries
73
- SET status = 'rejected', reviewed_at = NOW(), updated_at = NOW(), reason = $1, reason_code = $2
74
- WHERE discovery_id = $3
75
- `, [reason || null, reasonCode || null, discoveryId]);
76
-
77
- return { old_state: oldStatus, new_state: 'rejected' };
78
- }
79
-
80
- if (action === 'observe') {
81
- await executeQuery(`
82
- UPDATE rapport.onboarding_discoveries
83
- SET status = 'observing', updated_at = NOW()
84
- WHERE discovery_id = $1
85
- `, [discoveryId]);
86
-
87
- return { old_state: oldStatus, new_state: 'observing' };
88
- }
89
-
90
- return null;
91
- }
92
-
93
- async function transitionStandard({ body, requestContext }) {
94
- try {
95
- const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
96
-
97
- if (!email) {
98
- return createErrorResponse(401, 'Authentication required');
99
- }
100
-
101
- const { standard_id: id, action, reason, reason_code: reasonCode } = body || {};
102
-
103
- if (!id) {
104
- return createErrorResponse(400, 'standard_id is required');
105
- }
106
-
107
- if (!action) {
108
- return createErrorResponse(400, 'action is required', {
109
- valid_actions: ['propose', 'approve', 'reject', 'observe', 'disable', 'deprecate', 'delete', 'enable', 'cancel_deprecation']
110
- });
111
- }
112
-
113
- // Try standard lifecycle transition first
114
- try {
115
- const result = await lifecycle.transition(id, action, email, reason);
116
-
117
- return createSuccessResponse({
118
- standard_id: result.standard_id,
119
- old_state: result.old_state,
120
- new_state: result.new_state,
121
- action: result.action,
122
- audit_entry: result.audit_entry
123
- }, `Standard transitioned from '${result.old_state}' to '${result.new_state}'`);
124
- } catch (lifecycleError) {
125
- // If pattern not found and action is valid for discoveries, try discovery transition
126
- if (lifecycleError.message.includes('not found') && DISCOVERY_ACTIONS.includes(action)) {
127
- const discoveryResult = await transitionDiscovery(id, action, email, reason, reasonCode);
128
-
129
- if (discoveryResult) {
130
- return createSuccessResponse({
131
- standard_id: id,
132
- old_state: discoveryResult.old_state,
133
- new_state: discoveryResult.new_state,
134
- action,
135
- pattern_id: discoveryResult.pattern_id || null
136
- }, `Discovery transitioned from '${discoveryResult.old_state}' to '${discoveryResult.new_state}'`);
137
- }
138
- }
139
-
140
- // Re-throw if not handled
141
- throw lifecycleError;
142
- }
143
-
144
- } catch (error) {
145
- console.error('Standards Transition Error:', error);
146
-
147
- if (error.message.includes('Invalid action')) {
148
- return createErrorResponse(400, error.message);
149
- }
150
- if (error.message.includes('not found')) {
151
- return createErrorResponse(404, error.message);
152
- }
153
- if (error.message.includes('Cannot perform')) {
154
- return createErrorResponse(409, error.message);
155
- }
156
-
157
- return createErrorResponse(500, 'Failed to transition standard');
158
- }
159
- }
160
-
161
- exports.handler = wrapHandler(transitionStandard);