@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.
- package/README.md +300 -0
- package/hooks/README.md +494 -0
- package/hooks/pre-compact.js +392 -0
- package/hooks/session-start.js +264 -0
- package/package.json +90 -0
- package/scripts/harvest.js +561 -0
- package/scripts/init-project.js +437 -0
- package/scripts/inject.js +388 -0
- package/src/collaboration/CollaborationPrompt.js +460 -0
- package/src/core/AlertEngine.js +813 -0
- package/src/core/AlertNotifier.js +363 -0
- package/src/core/CorrelationAnalyzer.js +774 -0
- package/src/core/CurationEngine.js +688 -0
- package/src/core/LLMPatternDetector.js +508 -0
- package/src/core/LoadBearingDetector.js +242 -0
- package/src/core/NotificationService.js +1032 -0
- package/src/core/PatternValidator.js +355 -0
- package/src/core/README.md +160 -0
- package/src/core/RapportOrchestrator.js +446 -0
- package/src/core/RelevanceDetector.js +577 -0
- package/src/core/StandardsIngestion.js +575 -0
- package/src/core/TeamLoadBearingDetector.js +431 -0
- package/src/database/dbOperations.js +105 -0
- package/src/handlers/activity/activityGetMe.js +98 -0
- package/src/handlers/activity/activityGetTeam.js +130 -0
- package/src/handlers/alerts/alertsAcknowledge.js +91 -0
- package/src/handlers/alerts/alertsGet.js +250 -0
- package/src/handlers/collaborators/collaboratorAdd.js +201 -0
- package/src/handlers/collaborators/collaboratorInvite.js +218 -0
- package/src/handlers/collaborators/collaboratorList.js +88 -0
- package/src/handlers/collaborators/collaboratorRemove.js +127 -0
- package/src/handlers/collaborators/inviteAccept.js +122 -0
- package/src/handlers/context/contextGet.js +57 -0
- package/src/handlers/context/invariantsGet.js +74 -0
- package/src/handlers/context/loopsGet.js +82 -0
- package/src/handlers/context/notesCreate.js +74 -0
- package/src/handlers/context/purposeGet.js +78 -0
- package/src/handlers/correlations/correlationsDeveloperGet.js +226 -0
- package/src/handlers/correlations/correlationsGet.js +93 -0
- package/src/handlers/correlations/correlationsProjectGet.js +161 -0
- package/src/handlers/github/githubConnectionStatus.js +49 -0
- package/src/handlers/github/githubDiscoverPatterns.js +364 -0
- package/src/handlers/github/githubOAuthCallback.js +166 -0
- package/src/handlers/github/githubOAuthStart.js +59 -0
- package/src/handlers/github/githubPatternsReview.js +109 -0
- package/src/handlers/github/githubReposList.js +105 -0
- package/src/handlers/helpers/checkSuperAdmin.js +85 -0
- package/src/handlers/helpers/dbOperations.js +53 -0
- package/src/handlers/helpers/errorHandler.js +49 -0
- package/src/handlers/helpers/index.js +106 -0
- package/src/handlers/helpers/lambdaWrapper.js +60 -0
- package/src/handlers/helpers/responseUtil.js +55 -0
- package/src/handlers/helpers/subscriptionTiers.js +1168 -0
- package/src/handlers/notifications/getPreferences.js +84 -0
- package/src/handlers/notifications/sendNotification.js +170 -0
- package/src/handlers/notifications/updatePreferences.js +316 -0
- package/src/handlers/patterns/patternUsagePost.js +182 -0
- package/src/handlers/patterns/patternViolationPost.js +185 -0
- package/src/handlers/projects/projectCreate.js +107 -0
- package/src/handlers/projects/projectDelete.js +82 -0
- package/src/handlers/projects/projectGet.js +95 -0
- package/src/handlers/projects/projectUpdate.js +118 -0
- package/src/handlers/reports/aiLeverage.js +206 -0
- package/src/handlers/reports/engineeringInvestment.js +132 -0
- package/src/handlers/reports/riskForecast.js +186 -0
- package/src/handlers/reports/standardsRoi.js +162 -0
- package/src/handlers/scheduled/analyzeCorrelations.js +178 -0
- package/src/handlers/scheduled/analyzeGitHistory.js +510 -0
- package/src/handlers/scheduled/generateAlerts.js +135 -0
- package/src/handlers/scheduled/refreshActivity.js +21 -0
- package/src/handlers/scheduled/scanCompliance.js +334 -0
- package/src/handlers/sessions/sessionEndPost.js +180 -0
- package/src/handlers/sessions/sessionStandardsPost.js +135 -0
- package/src/handlers/stripe/addonManagePost.js +240 -0
- package/src/handlers/stripe/billingPortalPost.js +93 -0
- package/src/handlers/stripe/enterpriseCheckoutPost.js +272 -0
- package/src/handlers/stripe/seatsUpdatePost.js +185 -0
- package/src/handlers/stripe/subscriptionCancelDelete.js +169 -0
- package/src/handlers/stripe/subscriptionCreatePost.js +221 -0
- package/src/handlers/stripe/subscriptionUpdatePut.js +163 -0
- package/src/handlers/stripe/webhookPost.js +454 -0
- package/src/handlers/users/cognitoPostConfirmation.js +150 -0
- package/src/handlers/users/userEntitlementsGet.js +89 -0
- package/src/handlers/users/userGet.js +114 -0
- package/src/handlers/webhooks/githubWebhook.js +223 -0
- 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 };
|