@equilateral_ai/mindmeld 3.0.0

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 (86) hide show
  1. package/README.md +300 -0
  2. package/hooks/README.md +494 -0
  3. package/hooks/pre-compact.js +392 -0
  4. package/hooks/session-start.js +264 -0
  5. package/package.json +90 -0
  6. package/scripts/harvest.js +561 -0
  7. package/scripts/init-project.js +437 -0
  8. package/scripts/inject.js +388 -0
  9. package/src/collaboration/CollaborationPrompt.js +460 -0
  10. package/src/core/AlertEngine.js +813 -0
  11. package/src/core/AlertNotifier.js +363 -0
  12. package/src/core/CorrelationAnalyzer.js +774 -0
  13. package/src/core/CurationEngine.js +688 -0
  14. package/src/core/LLMPatternDetector.js +508 -0
  15. package/src/core/LoadBearingDetector.js +242 -0
  16. package/src/core/NotificationService.js +1032 -0
  17. package/src/core/PatternValidator.js +355 -0
  18. package/src/core/README.md +160 -0
  19. package/src/core/RapportOrchestrator.js +446 -0
  20. package/src/core/RelevanceDetector.js +577 -0
  21. package/src/core/StandardsIngestion.js +575 -0
  22. package/src/core/TeamLoadBearingDetector.js +431 -0
  23. package/src/database/dbOperations.js +105 -0
  24. package/src/handlers/activity/activityGetMe.js +98 -0
  25. package/src/handlers/activity/activityGetTeam.js +130 -0
  26. package/src/handlers/alerts/alertsAcknowledge.js +91 -0
  27. package/src/handlers/alerts/alertsGet.js +250 -0
  28. package/src/handlers/collaborators/collaboratorAdd.js +201 -0
  29. package/src/handlers/collaborators/collaboratorInvite.js +218 -0
  30. package/src/handlers/collaborators/collaboratorList.js +88 -0
  31. package/src/handlers/collaborators/collaboratorRemove.js +127 -0
  32. package/src/handlers/collaborators/inviteAccept.js +122 -0
  33. package/src/handlers/context/contextGet.js +57 -0
  34. package/src/handlers/context/invariantsGet.js +74 -0
  35. package/src/handlers/context/loopsGet.js +82 -0
  36. package/src/handlers/context/notesCreate.js +74 -0
  37. package/src/handlers/context/purposeGet.js +78 -0
  38. package/src/handlers/correlations/correlationsDeveloperGet.js +226 -0
  39. package/src/handlers/correlations/correlationsGet.js +93 -0
  40. package/src/handlers/correlations/correlationsProjectGet.js +161 -0
  41. package/src/handlers/github/githubConnectionStatus.js +49 -0
  42. package/src/handlers/github/githubDiscoverPatterns.js +364 -0
  43. package/src/handlers/github/githubOAuthCallback.js +166 -0
  44. package/src/handlers/github/githubOAuthStart.js +59 -0
  45. package/src/handlers/github/githubPatternsReview.js +109 -0
  46. package/src/handlers/github/githubReposList.js +105 -0
  47. package/src/handlers/helpers/checkSuperAdmin.js +85 -0
  48. package/src/handlers/helpers/dbOperations.js +53 -0
  49. package/src/handlers/helpers/errorHandler.js +49 -0
  50. package/src/handlers/helpers/index.js +106 -0
  51. package/src/handlers/helpers/lambdaWrapper.js +60 -0
  52. package/src/handlers/helpers/responseUtil.js +55 -0
  53. package/src/handlers/helpers/subscriptionTiers.js +1168 -0
  54. package/src/handlers/notifications/getPreferences.js +84 -0
  55. package/src/handlers/notifications/sendNotification.js +170 -0
  56. package/src/handlers/notifications/updatePreferences.js +316 -0
  57. package/src/handlers/patterns/patternUsagePost.js +182 -0
  58. package/src/handlers/patterns/patternViolationPost.js +185 -0
  59. package/src/handlers/projects/projectCreate.js +107 -0
  60. package/src/handlers/projects/projectDelete.js +82 -0
  61. package/src/handlers/projects/projectGet.js +95 -0
  62. package/src/handlers/projects/projectUpdate.js +118 -0
  63. package/src/handlers/reports/aiLeverage.js +206 -0
  64. package/src/handlers/reports/engineeringInvestment.js +132 -0
  65. package/src/handlers/reports/riskForecast.js +186 -0
  66. package/src/handlers/reports/standardsRoi.js +162 -0
  67. package/src/handlers/scheduled/analyzeCorrelations.js +178 -0
  68. package/src/handlers/scheduled/analyzeGitHistory.js +510 -0
  69. package/src/handlers/scheduled/generateAlerts.js +135 -0
  70. package/src/handlers/scheduled/refreshActivity.js +21 -0
  71. package/src/handlers/scheduled/scanCompliance.js +334 -0
  72. package/src/handlers/sessions/sessionEndPost.js +180 -0
  73. package/src/handlers/sessions/sessionStandardsPost.js +135 -0
  74. package/src/handlers/stripe/addonManagePost.js +240 -0
  75. package/src/handlers/stripe/billingPortalPost.js +93 -0
  76. package/src/handlers/stripe/enterpriseCheckoutPost.js +272 -0
  77. package/src/handlers/stripe/seatsUpdatePost.js +185 -0
  78. package/src/handlers/stripe/subscriptionCancelDelete.js +169 -0
  79. package/src/handlers/stripe/subscriptionCreatePost.js +221 -0
  80. package/src/handlers/stripe/subscriptionUpdatePut.js +163 -0
  81. package/src/handlers/stripe/webhookPost.js +454 -0
  82. package/src/handlers/users/cognitoPostConfirmation.js +150 -0
  83. package/src/handlers/users/userEntitlementsGet.js +89 -0
  84. package/src/handlers/users/userGet.js +114 -0
  85. package/src/handlers/webhooks/githubWebhook.js +223 -0
  86. package/src/index.js +969 -0
@@ -0,0 +1,577 @@
1
+ /**
2
+ * RelevanceDetector - Phase 2: Rapport Standards Integration
3
+ *
4
+ * Identifies which standards apply to current work by:
5
+ * 1. Detecting project characteristics (Lambda, React, SAM, etc.)
6
+ * 2. Mapping characteristics to standard categories
7
+ * 3. Querying relevant standards from database
8
+ * 4. Returning top 10 most relevant standards with scoring
9
+ *
10
+ * Target: < 500ms execution time
11
+ *
12
+ * @module RelevanceDetector
13
+ */
14
+
15
+ const fs = require('fs').promises;
16
+ const path = require('path');
17
+ const { executeQuery } = require('../handlers/helpers/dbOperations');
18
+
19
+ class RelevanceDetector {
20
+ constructor(options = {}) {
21
+ this.workingDirectory = options.workingDirectory || process.cwd();
22
+ this.standardsPath = options.standardsPath || '.equilateral-standards';
23
+
24
+ // Category weights for relevance scoring
25
+ this.categoryWeights = {
26
+ 'serverless-saas-aws': 1.0,
27
+ 'multi-agent-orchestration': 1.0,
28
+ 'frontend-development': 1.0,
29
+ 'database': 0.9,
30
+ 'compliance-security': 0.8,
31
+ 'cost-optimization': 0.7,
32
+ 'real-time-systems': 0.8,
33
+ 'testing': 0.6,
34
+ 'backend': 0.9,
35
+ 'well-architected': 0.7
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Main entry point: Detect relevant standards for current project context
41
+ * @param {Object} projectContext - Context information about the project
42
+ * @returns {Promise<Object>} Relevant standards with scoring
43
+ */
44
+ async detectRelevantStandards(projectContext = {}) {
45
+ const startTime = Date.now();
46
+
47
+ try {
48
+ // 1. Analyze project structure to detect characteristics
49
+ const characteristics = await this.detectProjectCharacteristics(projectContext);
50
+
51
+ // 2. Map characteristics to standard categories
52
+ const relevantCategories = this.mapCharacteristicsToCategories(characteristics);
53
+
54
+ // 3. Query standards from database (assumes Phase 1 schema exists)
55
+ const standards = await this.queryRelevantStandards({
56
+ categories: relevantCategories,
57
+ currentFile: projectContext.currentFile,
58
+ characteristics: characteristics
59
+ });
60
+
61
+ // 4. Calculate relevance scores and rank
62
+ const rankedStandards = this.rankStandards(standards, characteristics, relevantCategories);
63
+
64
+ // 5. Return top 10 most relevant
65
+ const topStandards = rankedStandards.slice(0, 10);
66
+
67
+ const executionTime = Date.now() - startTime;
68
+
69
+ return {
70
+ characteristics,
71
+ categories: relevantCategories,
72
+ standards: topStandards,
73
+ totalStandards: standards.length,
74
+ executionTime,
75
+ timestamp: new Date().toISOString()
76
+ };
77
+ } catch (error) {
78
+ console.error('RelevanceDetector error:', error);
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Detect project characteristics from file structure and content
85
+ * Fast file system analysis without deep content parsing
86
+ */
87
+ async detectProjectCharacteristics(projectContext) {
88
+ const characteristics = {
89
+ hasLambda: false,
90
+ hasReact: false,
91
+ hasDatabase: false,
92
+ hasMultiAgent: false,
93
+ hasSAM: false,
94
+ hasAPI: false,
95
+ hasTypeScript: false,
96
+ hasNodeJS: false,
97
+ hasPython: false,
98
+ hasDocker: false,
99
+ hasCI: false,
100
+ hasTests: false,
101
+ framework: null,
102
+ deploymentType: null
103
+ };
104
+
105
+ try {
106
+ // Check for key files that indicate project type
107
+ const keyFiles = await this.checkKeyFiles();
108
+
109
+ // SAM/Serverless detection
110
+ characteristics.hasSAM = keyFiles.includes('template.yaml') || keyFiles.includes('template.yml');
111
+
112
+ // Lambda detection (SAM template or serverless.yml)
113
+ if (characteristics.hasSAM || keyFiles.includes('serverless.yml')) {
114
+ characteristics.hasLambda = await this.detectLambdaFunctions();
115
+ }
116
+
117
+ // React detection
118
+ characteristics.hasReact = await this.detectReact(keyFiles);
119
+
120
+ // Database detection
121
+ characteristics.hasDatabase = await this.detectDatabase(keyFiles);
122
+
123
+ // Multi-agent detection
124
+ characteristics.hasMultiAgent = await this.detectMultiAgent();
125
+
126
+ // API detection
127
+ characteristics.hasAPI = await this.detectAPIHandlers();
128
+
129
+ // Language detection
130
+ characteristics.hasTypeScript = keyFiles.some(f => f.endsWith('.ts') || f.endsWith('.tsx'));
131
+ characteristics.hasNodeJS = keyFiles.includes('package.json');
132
+ characteristics.hasPython = keyFiles.includes('requirements.txt') || keyFiles.includes('setup.py');
133
+
134
+ // Infrastructure detection
135
+ characteristics.hasDocker = keyFiles.includes('Dockerfile') || keyFiles.includes('docker-compose.yml');
136
+ characteristics.hasCI = keyFiles.includes('.github') || keyFiles.includes('.gitlab-ci.yml');
137
+ characteristics.hasTests = keyFiles.includes('tests') || keyFiles.includes('test');
138
+
139
+ // Determine framework
140
+ characteristics.framework = await this.detectFramework(keyFiles);
141
+
142
+ // Determine deployment type
143
+ characteristics.deploymentType = this.determineDeploymentType(characteristics);
144
+
145
+ return characteristics;
146
+ } catch (error) {
147
+ console.error('Error detecting characteristics:', error);
148
+ return characteristics; // Return partial results
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Check for key files in project
154
+ */
155
+ async checkKeyFiles() {
156
+ try {
157
+ const files = await fs.readdir(this.workingDirectory);
158
+
159
+ // Also check common subdirectories
160
+ const checkDirs = ['src', 'handlers', 'tests', 'test', '.github'];
161
+ for (const dir of checkDirs) {
162
+ try {
163
+ const dirPath = path.join(this.workingDirectory, dir);
164
+ const stats = await fs.stat(dirPath);
165
+ if (stats.isDirectory()) {
166
+ files.push(dir);
167
+ }
168
+ } catch (e) {
169
+ // Directory doesn't exist, skip
170
+ }
171
+ }
172
+
173
+ return files;
174
+ } catch (error) {
175
+ console.error('Error reading directory:', error);
176
+ return [];
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Detect Lambda functions by checking for handler files or SAM config
182
+ */
183
+ async detectLambdaFunctions() {
184
+ try {
185
+ // Check for SAM template with Lambda functions
186
+ const templatePath = path.join(this.workingDirectory, 'template.yaml');
187
+ try {
188
+ const content = await fs.readFile(templatePath, 'utf-8');
189
+ return content.includes('AWS::Serverless::Function') ||
190
+ content.includes('AWS::Lambda::Function');
191
+ } catch (e) {
192
+ // File doesn't exist, check for handler files
193
+ }
194
+
195
+ // Check for common Lambda handler directories
196
+ const handlerDirs = ['src/handlers', 'handlers', 'functions', 'lambdas'];
197
+ for (const dir of handlerDirs) {
198
+ try {
199
+ const dirPath = path.join(this.workingDirectory, dir);
200
+ await fs.access(dirPath);
201
+ return true; // Directory exists
202
+ } catch (e) {
203
+ continue;
204
+ }
205
+ }
206
+
207
+ return false;
208
+ } catch (error) {
209
+ return false;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Detect React usage
215
+ */
216
+ async detectReact(keyFiles) {
217
+ if (!keyFiles.includes('package.json')) {
218
+ return false;
219
+ }
220
+
221
+ try {
222
+ const pkgPath = path.join(this.workingDirectory, 'package.json');
223
+ const content = await fs.readFile(pkgPath, 'utf-8');
224
+ const pkg = JSON.parse(content);
225
+
226
+ return !!(pkg.dependencies?.react || pkg.devDependencies?.react);
227
+ } catch (error) {
228
+ // Check for .jsx or .tsx files
229
+ return keyFiles.some(f => f.endsWith('.jsx') || f.endsWith('.tsx'));
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Detect database usage
235
+ */
236
+ async detectDatabase(keyFiles) {
237
+ // Check for database configuration files
238
+ if (keyFiles.includes('schema.sql') ||
239
+ keyFiles.includes('database') ||
240
+ keyFiles.some(f => f.includes('migration'))) {
241
+ return true;
242
+ }
243
+
244
+ // Check package.json for database libraries
245
+ if (keyFiles.includes('package.json')) {
246
+ try {
247
+ const pkgPath = path.join(this.workingDirectory, 'package.json');
248
+ const content = await fs.readFile(pkgPath, 'utf-8');
249
+ const pkg = JSON.parse(content);
250
+
251
+ const dbLibs = ['pg', 'mysql', 'mongodb', 'mongoose', 'sequelize', 'typeorm'];
252
+ return dbLibs.some(lib =>
253
+ pkg.dependencies?.[lib] || pkg.devDependencies?.[lib]
254
+ );
255
+ } catch (error) {
256
+ return false;
257
+ }
258
+ }
259
+
260
+ return false;
261
+ }
262
+
263
+ /**
264
+ * Detect multi-agent orchestration patterns
265
+ */
266
+ async detectMultiAgent() {
267
+ try {
268
+ // Check for agent-related directories
269
+ const agentDirs = ['agents', 'src/agents', 'src/core'];
270
+ for (const dir of agentDirs) {
271
+ try {
272
+ const dirPath = path.join(this.workingDirectory, dir);
273
+ const files = await fs.readdir(dirPath);
274
+
275
+ // Look for agent-related files
276
+ if (files.some(f =>
277
+ f.includes('Agent') ||
278
+ f.includes('Orchestrat') ||
279
+ f.includes('agent')
280
+ )) {
281
+ return true;
282
+ }
283
+ } catch (e) {
284
+ continue;
285
+ }
286
+ }
287
+
288
+ return false;
289
+ } catch (error) {
290
+ return false;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Detect API handlers
296
+ */
297
+ async detectAPIHandlers() {
298
+ try {
299
+ const handlerDirs = ['src/handlers', 'handlers', 'api'];
300
+ for (const dir of handlerDirs) {
301
+ try {
302
+ const dirPath = path.join(this.workingDirectory, dir);
303
+ await fs.access(dirPath);
304
+ return true;
305
+ } catch (e) {
306
+ continue;
307
+ }
308
+ }
309
+
310
+ return false;
311
+ } catch (error) {
312
+ return false;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Detect framework being used
318
+ */
319
+ async detectFramework(keyFiles) {
320
+ if (!keyFiles.includes('package.json')) {
321
+ return null;
322
+ }
323
+
324
+ try {
325
+ const pkgPath = path.join(this.workingDirectory, 'package.json');
326
+ const content = await fs.readFile(pkgPath, 'utf-8');
327
+ const pkg = JSON.parse(content);
328
+
329
+ const frameworks = {
330
+ 'next': 'Next.js',
331
+ 'nuxt': 'Nuxt.js',
332
+ 'express': 'Express',
333
+ 'fastify': 'Fastify',
334
+ 'nestjs': 'NestJS',
335
+ 'react': 'React',
336
+ 'vue': 'Vue.js',
337
+ 'angular': 'Angular'
338
+ };
339
+
340
+ for (const [key, name] of Object.entries(frameworks)) {
341
+ if (pkg.dependencies?.[key] || pkg.devDependencies?.[key]) {
342
+ return name;
343
+ }
344
+ }
345
+
346
+ return null;
347
+ } catch (error) {
348
+ return null;
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Determine deployment type from characteristics
354
+ */
355
+ determineDeploymentType(characteristics) {
356
+ if (characteristics.hasSAM || characteristics.hasLambda) {
357
+ return 'serverless';
358
+ }
359
+ if (characteristics.hasDocker) {
360
+ return 'containerized';
361
+ }
362
+ if (characteristics.hasNodeJS || characteristics.hasPython) {
363
+ return 'traditional';
364
+ }
365
+ return 'unknown';
366
+ }
367
+
368
+ /**
369
+ * Map detected characteristics to standard categories
370
+ */
371
+ mapCharacteristicsToCategories(characteristics) {
372
+ const categories = new Set();
373
+
374
+ // Serverless/Lambda → serverless-saas-aws
375
+ if (characteristics.hasLambda || characteristics.hasSAM) {
376
+ categories.add('serverless-saas-aws');
377
+ }
378
+
379
+ // React/Frontend → frontend-development
380
+ if (characteristics.hasReact) {
381
+ categories.add('frontend-development');
382
+ }
383
+
384
+ // Database → database
385
+ if (characteristics.hasDatabase) {
386
+ categories.add('database');
387
+ }
388
+
389
+ // Multi-agent → multi-agent-orchestration
390
+ if (characteristics.hasMultiAgent) {
391
+ categories.add('multi-agent-orchestration');
392
+ }
393
+
394
+ // API handlers → backend
395
+ if (characteristics.hasAPI) {
396
+ categories.add('backend');
397
+ }
398
+
399
+ // Cost optimization always relevant for AWS
400
+ if (characteristics.hasLambda || characteristics.hasSAM) {
401
+ categories.add('cost-optimization');
402
+ }
403
+
404
+ // Testing if tests detected
405
+ if (characteristics.hasTests) {
406
+ categories.add('testing');
407
+ }
408
+
409
+ // Security/compliance always relevant
410
+ categories.add('compliance-security');
411
+
412
+ // Well-architected principles always relevant
413
+ categories.add('well-architected');
414
+
415
+ return Array.from(categories);
416
+ }
417
+
418
+ /**
419
+ * Query relevant standards from database
420
+ * Assumes Phase 1 schema (rapport.standards_patterns table exists)
421
+ */
422
+ async queryRelevantStandards({ categories, currentFile, characteristics }) {
423
+ try {
424
+ // Build file type filter
425
+ let fileType = null;
426
+ if (currentFile) {
427
+ const ext = path.extname(currentFile);
428
+ if (ext) {
429
+ fileType = `*${ext}`;
430
+ }
431
+ }
432
+
433
+ // Query standards matching categories
434
+ const query = `
435
+ SELECT
436
+ pattern_id,
437
+ element,
438
+ rule,
439
+ category,
440
+ correlation,
441
+ maturity,
442
+ applicable_files,
443
+ anti_patterns,
444
+ examples,
445
+ cost_impact,
446
+ source
447
+ FROM rapport.standards_patterns
448
+ WHERE category = ANY($1::varchar[])
449
+ AND maturity IN ('enforced', 'validated', 'recommended')
450
+ ORDER BY
451
+ CASE
452
+ WHEN maturity = 'enforced' THEN 1
453
+ WHEN maturity = 'validated' THEN 2
454
+ ELSE 3
455
+ END,
456
+ correlation DESC
457
+ `;
458
+
459
+ const result = await executeQuery(query, [categories]);
460
+ return result.rows;
461
+ } catch (error) {
462
+ // If table doesn't exist (Phase 1 not complete), return empty array
463
+ console.warn('Standards patterns table not found. Assuming Phase 1 not complete.');
464
+ return [];
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Rank standards by relevance score
470
+ */
471
+ rankStandards(standards, characteristics, categories) {
472
+ return standards.map(standard => {
473
+ let score = 0;
474
+
475
+ // Base score from correlation
476
+ score += (standard.correlation || 1.0) * 40;
477
+
478
+ // Maturity score
479
+ const maturityScores = {
480
+ 'enforced': 30,
481
+ 'validated': 20,
482
+ 'recommended': 10,
483
+ 'provisional': 5
484
+ };
485
+ score += maturityScores[standard.maturity] || 0;
486
+
487
+ // Category weight
488
+ const categoryWeight = this.categoryWeights[standard.category] || 0.5;
489
+ score += categoryWeight * 20;
490
+
491
+ // File applicability bonus
492
+ if (standard.applicable_files && standard.applicable_files.length > 0) {
493
+ score += 5;
494
+ }
495
+
496
+ // Cost impact bonus (critical patterns)
497
+ if (standard.cost_impact && standard.cost_impact.severity === 'critical') {
498
+ score += 10;
499
+ }
500
+
501
+ // Anti-patterns bonus (important to know what NOT to do)
502
+ if (standard.anti_patterns && Object.keys(standard.anti_patterns).length > 0) {
503
+ score += 5;
504
+ }
505
+
506
+ return {
507
+ ...standard,
508
+ relevance_score: Math.round(score * 10) / 10 // Round to 1 decimal
509
+ };
510
+ }).sort((a, b) => b.relevance_score - a.relevance_score);
511
+ }
512
+
513
+ /**
514
+ * Format standards for injection into Claude Code context
515
+ */
516
+ formatForInjection(relevantStandards) {
517
+ if (!relevantStandards || relevantStandards.length === 0) {
518
+ return '';
519
+ }
520
+
521
+ let output = '## Relevant Standards (Auto-Detected)\n\n';
522
+
523
+ for (const standard of relevantStandards) {
524
+ output += `### ${standard.element}\n`;
525
+ output += `**Category**: ${standard.category}\n`;
526
+ output += `**Maturity**: ${standard.maturity}\n`;
527
+ output += `**Relevance**: ${standard.relevance_score}/100\n\n`;
528
+ output += `${standard.rule}\n\n`;
529
+
530
+ // Include examples if available
531
+ if (standard.examples && standard.examples.length > 0) {
532
+ output += '**Examples**:\n';
533
+ for (const example of standard.examples.slice(0, 2)) { // Max 2 examples
534
+ output += `\`\`\`javascript\n${example.code || example}\n\`\`\`\n\n`;
535
+ }
536
+ }
537
+
538
+ // Include anti-patterns if available
539
+ if (standard.anti_patterns && Object.keys(standard.anti_patterns).length > 0) {
540
+ output += '**Anti-Patterns** (Avoid):\n';
541
+ for (const [key, value] of Object.entries(standard.anti_patterns)) {
542
+ output += `- ❌ ${key}: ${value}\n`;
543
+ }
544
+ output += '\n';
545
+ }
546
+
547
+ output += '---\n\n';
548
+ }
549
+
550
+ return output;
551
+ }
552
+
553
+ /**
554
+ * Get execution statistics
555
+ */
556
+ async getStatistics() {
557
+ try {
558
+ const query = `
559
+ SELECT
560
+ category,
561
+ maturity,
562
+ COUNT(*) as count
563
+ FROM rapport.standards_patterns
564
+ GROUP BY category, maturity
565
+ ORDER BY category, maturity
566
+ `;
567
+
568
+ const result = await executeQuery(query);
569
+ return result.rows;
570
+ } catch (error) {
571
+ console.error('Error getting statistics:', error);
572
+ return [];
573
+ }
574
+ }
575
+ }
576
+
577
+ module.exports = { RelevanceDetector };