@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,575 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StandardsIngestion.js - Phase 1 of Rapport Standards Integration
|
|
3
|
+
*
|
|
4
|
+
* Parses and ingests .equilateral-standards/ markdown files into Rapport database
|
|
5
|
+
* Creates standards_patterns from enforcement rules, anti-patterns, examples, and cost impacts
|
|
6
|
+
*
|
|
7
|
+
* Reference: /Users/jamesford/Source/rapport/docs/RAPPORT_STANDARDS_INTEGRATION_DESIGN.md
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs').promises;
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { executeQuery } = require('../handlers/helpers/dbOperations');
|
|
13
|
+
|
|
14
|
+
class StandardsIngestion {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.standardsPath = options.standardsPath || path.join(process.cwd(), '.equilateral-standards');
|
|
17
|
+
this.verbose = options.verbose || false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Main ingestion entry point
|
|
22
|
+
* Discovers and parses all .equilateral-standards/ markdown files
|
|
23
|
+
*/
|
|
24
|
+
async ingestEquilateralStandards() {
|
|
25
|
+
console.log(`[StandardsIngestion] Starting ingestion from ${this.standardsPath}`);
|
|
26
|
+
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
const patterns = [];
|
|
29
|
+
|
|
30
|
+
// Define standard categories
|
|
31
|
+
const categories = [
|
|
32
|
+
'serverless-saas-aws',
|
|
33
|
+
'multi-agent-orchestration',
|
|
34
|
+
'compliance-security',
|
|
35
|
+
'database',
|
|
36
|
+
'frontend-development',
|
|
37
|
+
'cost-optimization',
|
|
38
|
+
'backend',
|
|
39
|
+
'deployment',
|
|
40
|
+
'real-time-systems',
|
|
41
|
+
'testing',
|
|
42
|
+
'well-architected'
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// Discover and parse all standards files
|
|
46
|
+
for (const category of categories) {
|
|
47
|
+
const categoryPath = path.join(this.standardsPath, category);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const files = await this.discoverStandardFiles(categoryPath);
|
|
51
|
+
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
try {
|
|
54
|
+
const standard = await this.parseStandard(file, category);
|
|
55
|
+
|
|
56
|
+
if (standard && standard.patterns.length > 0) {
|
|
57
|
+
patterns.push(...standard.patterns);
|
|
58
|
+
|
|
59
|
+
if (this.verbose) {
|
|
60
|
+
console.log(` ✓ Parsed ${path.basename(file)}: ${standard.patterns.length} patterns`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (parseError) {
|
|
64
|
+
console.warn(` ⚠ Failed to parse ${path.basename(file)}:`, parseError.message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch (categoryError) {
|
|
68
|
+
if (categoryError.code !== 'ENOENT') {
|
|
69
|
+
console.warn(` ⚠ Failed to process category ${category}:`, categoryError.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Store in database
|
|
75
|
+
const ingestedCount = await this.seedPatternDatabase(patterns);
|
|
76
|
+
|
|
77
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
patternsIngested: ingestedCount,
|
|
81
|
+
totalPatternsFound: patterns.length,
|
|
82
|
+
categories: categories.length,
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
duration: `${duration}s`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Discover all markdown files in a category directory
|
|
90
|
+
*/
|
|
91
|
+
async discoverStandardFiles(categoryPath) {
|
|
92
|
+
try {
|
|
93
|
+
const entries = await fs.readdir(categoryPath, { withFileTypes: true });
|
|
94
|
+
const files = [];
|
|
95
|
+
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
const fullPath = path.join(categoryPath, entry.name);
|
|
98
|
+
|
|
99
|
+
if (entry.isDirectory()) {
|
|
100
|
+
// Recursively search subdirectories
|
|
101
|
+
const subFiles = await this.discoverStandardFiles(fullPath);
|
|
102
|
+
files.push(...subFiles);
|
|
103
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
104
|
+
files.push(fullPath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return files;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error.code === 'ENOENT') {
|
|
111
|
+
// Category doesn't exist - not an error
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse a markdown standards file into Rapport pattern format
|
|
120
|
+
*/
|
|
121
|
+
async parseStandard(filepath, category) {
|
|
122
|
+
const content = await fs.readFile(filepath, 'utf-8');
|
|
123
|
+
const filename = path.basename(filepath, '.md');
|
|
124
|
+
|
|
125
|
+
// Extract standard metadata from markdown
|
|
126
|
+
const title = this.extractTitle(content);
|
|
127
|
+
const enforcementRules = this.extractEnforcementRules(content);
|
|
128
|
+
const antiPatterns = this.extractAntiPatterns(content);
|
|
129
|
+
const examples = this.extractExamples(content);
|
|
130
|
+
const costImpact = this.extractCostImpact(content);
|
|
131
|
+
const applicableFiles = this.extractApplicableFiles(content, category);
|
|
132
|
+
|
|
133
|
+
const patterns = [];
|
|
134
|
+
|
|
135
|
+
// Create pattern for each enforcement rule
|
|
136
|
+
for (const rule of enforcementRules) {
|
|
137
|
+
const patternId = this.generatePatternId(category, filename, rule.element);
|
|
138
|
+
|
|
139
|
+
patterns.push({
|
|
140
|
+
pattern_id: patternId,
|
|
141
|
+
element: rule.element,
|
|
142
|
+
rule: rule.rule,
|
|
143
|
+
correlation: 1.0, // Standards are mandatory
|
|
144
|
+
source: 'equilateral-standards',
|
|
145
|
+
maturity: rule.severity === 'MANDATORY' || rule.severity === 'CRITICAL' ? 'enforced' : 'validated',
|
|
146
|
+
scope: 'organization',
|
|
147
|
+
category: category,
|
|
148
|
+
applicable_files: applicableFiles,
|
|
149
|
+
anti_patterns: antiPatterns,
|
|
150
|
+
examples: examples,
|
|
151
|
+
cost_impact: costImpact,
|
|
152
|
+
source_file: filepath,
|
|
153
|
+
title: title
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { patterns };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Extract title from markdown (first H1)
|
|
162
|
+
*/
|
|
163
|
+
extractTitle(content) {
|
|
164
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
165
|
+
return match ? match[1].trim() : 'Untitled Standard';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extract enforcement rules from markdown
|
|
170
|
+
* Looks for sections like "MANDATORY PATTERNS", "Core Principles", etc.
|
|
171
|
+
*/
|
|
172
|
+
extractEnforcementRules(content) {
|
|
173
|
+
const rules = [];
|
|
174
|
+
|
|
175
|
+
// Pattern 1: MANDATORY/CRITICAL sections
|
|
176
|
+
const mandatorySection = content.match(/##?\s*⚠?️?\s*(MANDATORY|CRITICAL)[^#]*/gi);
|
|
177
|
+
if (mandatorySection) {
|
|
178
|
+
for (const section of mandatorySection) {
|
|
179
|
+
const sectionRules = this.extractRulesFromSection(section, 'MANDATORY');
|
|
180
|
+
rules.push(...sectionRules);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Pattern 2: "ALWAYS" and "NEVER" statements
|
|
185
|
+
const alwaysNever = content.match(/(?:ALWAYS|NEVER)\s+([^.\n]+)/g);
|
|
186
|
+
if (alwaysNever) {
|
|
187
|
+
for (const statement of alwaysNever) {
|
|
188
|
+
rules.push({
|
|
189
|
+
element: this.extractElement(statement),
|
|
190
|
+
rule: statement.trim(),
|
|
191
|
+
severity: 'MANDATORY'
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Pattern 3: Core Principles
|
|
197
|
+
const principleMatch = content.match(/##?\s*Core Principle[s]?[:\s]+([^#]+)/i);
|
|
198
|
+
if (principleMatch) {
|
|
199
|
+
rules.push({
|
|
200
|
+
element: 'Core Principle',
|
|
201
|
+
rule: principleMatch[1].trim(),
|
|
202
|
+
severity: 'ENFORCED'
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return rules.length > 0 ? rules : [{
|
|
207
|
+
element: 'Standard Pattern',
|
|
208
|
+
rule: this.extractFirstParagraph(content),
|
|
209
|
+
severity: 'VALIDATED'
|
|
210
|
+
}];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Extract rules from a section of content
|
|
215
|
+
*/
|
|
216
|
+
extractRulesFromSection(section, severity) {
|
|
217
|
+
const rules = [];
|
|
218
|
+
|
|
219
|
+
// Look for numbered or bulleted lists
|
|
220
|
+
const listItems = section.match(/^[\s]*[-*\d]+\.?\s+(.+)$/gm);
|
|
221
|
+
if (listItems) {
|
|
222
|
+
for (const item of listItems) {
|
|
223
|
+
const cleaned = item.replace(/^[\s]*[-*\d]+\.?\s+/, '').trim();
|
|
224
|
+
if (cleaned.length > 10) { // Filter out very short items
|
|
225
|
+
rules.push({
|
|
226
|
+
element: this.extractElement(cleaned),
|
|
227
|
+
rule: cleaned,
|
|
228
|
+
severity: severity
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Look for bold statements
|
|
235
|
+
const boldStatements = section.match(/\*\*(.+?)\*\*/g);
|
|
236
|
+
if (boldStatements && rules.length === 0) {
|
|
237
|
+
for (const statement of boldStatements) {
|
|
238
|
+
const cleaned = statement.replace(/\*\*/g, '').trim();
|
|
239
|
+
if (cleaned.length > 10 && !cleaned.match(/^(MANDATORY|CRITICAL|NOTE|IMPORTANT)/i)) {
|
|
240
|
+
rules.push({
|
|
241
|
+
element: this.extractElement(cleaned),
|
|
242
|
+
rule: cleaned,
|
|
243
|
+
severity: severity
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return rules;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Extract anti-patterns from markdown
|
|
254
|
+
* Looks for ❌ WRONG, NEVER DO THIS, Anti-Pattern sections
|
|
255
|
+
*/
|
|
256
|
+
extractAntiPatterns(content) {
|
|
257
|
+
const antiPatterns = [];
|
|
258
|
+
|
|
259
|
+
// Pattern 1: ❌ markers
|
|
260
|
+
const wrongPatterns = content.match(/❌[^```]*?```[^`]+```/gs);
|
|
261
|
+
if (wrongPatterns) {
|
|
262
|
+
for (const pattern of wrongPatterns) {
|
|
263
|
+
const description = pattern.match(/❌\s*([^\n`]+)/);
|
|
264
|
+
const code = pattern.match(/```(\w+)?\n([\s\S]+?)```/);
|
|
265
|
+
|
|
266
|
+
if (description && code) {
|
|
267
|
+
antiPatterns.push({
|
|
268
|
+
description: description[1].trim(),
|
|
269
|
+
code: code[2].trim(),
|
|
270
|
+
language: code[1] || 'javascript'
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Pattern 2: Anti-Pattern sections
|
|
277
|
+
const antiPatternSection = content.match(/##?\s*Anti-Pattern[s]?[^#]*/gi);
|
|
278
|
+
if (antiPatternSection) {
|
|
279
|
+
for (const section of antiPatternSection) {
|
|
280
|
+
const sectionPatterns = this.extractCodeBlocksFromSection(section);
|
|
281
|
+
antiPatterns.push(...sectionPatterns.map(p => ({
|
|
282
|
+
description: p.description || 'Anti-pattern to avoid',
|
|
283
|
+
code: p.code,
|
|
284
|
+
language: p.language
|
|
285
|
+
})));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return antiPatterns;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Extract examples from markdown
|
|
294
|
+
* Looks for ✅ CORRECT, Example sections
|
|
295
|
+
*/
|
|
296
|
+
extractExamples(content) {
|
|
297
|
+
const examples = [];
|
|
298
|
+
|
|
299
|
+
// Pattern 1: ✅ markers
|
|
300
|
+
const correctPatterns = content.match(/✅[^```]*?```[^`]+```/gs);
|
|
301
|
+
if (correctPatterns) {
|
|
302
|
+
for (const pattern of correctPatterns) {
|
|
303
|
+
const description = pattern.match(/✅\s*([^\n`]+)/);
|
|
304
|
+
const code = pattern.match(/```(\w+)?\n([\s\S]+?)```/);
|
|
305
|
+
|
|
306
|
+
if (description && code) {
|
|
307
|
+
examples.push({
|
|
308
|
+
description: description[1].trim(),
|
|
309
|
+
code: code[2].trim(),
|
|
310
|
+
language: code[1] || 'javascript'
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Pattern 2: Example sections
|
|
317
|
+
const exampleSection = content.match(/##?\s*Example[s]?[^#]*/gi);
|
|
318
|
+
if (exampleSection) {
|
|
319
|
+
for (const section of exampleSection) {
|
|
320
|
+
const sectionExamples = this.extractCodeBlocksFromSection(section);
|
|
321
|
+
examples.push(...sectionExamples.map(p => ({
|
|
322
|
+
description: p.description || 'Example implementation',
|
|
323
|
+
code: p.code,
|
|
324
|
+
language: p.language
|
|
325
|
+
})));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return examples;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Extract cost impact information
|
|
334
|
+
*/
|
|
335
|
+
extractCostImpact(content) {
|
|
336
|
+
const costImpact = {};
|
|
337
|
+
|
|
338
|
+
// Look for cost comparison tables
|
|
339
|
+
const costTable = content.match(/\|[^|]*Cost[^|]*\|[\s\S]*?\n\n/i);
|
|
340
|
+
if (costTable) {
|
|
341
|
+
costImpact.hasTable = true;
|
|
342
|
+
costImpact.details = costTable[0].trim();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Look for specific cost mentions
|
|
346
|
+
const costMentions = content.match(/\$\d+[^.\n]*/g);
|
|
347
|
+
if (costMentions) {
|
|
348
|
+
costImpact.mentions = costMentions.map(m => m.trim());
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Look for "Cost Impact" sections
|
|
352
|
+
const costSection = content.match(/##?\s*Cost Impact[^#]*/i);
|
|
353
|
+
if (costSection) {
|
|
354
|
+
costImpact.section = costSection[0].trim();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return Object.keys(costImpact).length > 0 ? costImpact : null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Determine applicable file patterns based on category and content
|
|
362
|
+
*/
|
|
363
|
+
extractApplicableFiles(content, category) {
|
|
364
|
+
const patterns = [];
|
|
365
|
+
|
|
366
|
+
// Category-based patterns
|
|
367
|
+
const categoryPatterns = {
|
|
368
|
+
'serverless-saas-aws': ['**/*.js', '**/template.yaml', '**/handlers/**/*.js'],
|
|
369
|
+
'frontend-development': ['**/*.tsx', '**/*.jsx', '**/*.ts', 'src/frontend/**/*'],
|
|
370
|
+
'database': ['**/*.sql', '**/dbOperations.js', '**/migrations/**/*'],
|
|
371
|
+
'backend': ['src/backend/**/*.js', 'src/handlers/**/*.js'],
|
|
372
|
+
'multi-agent-orchestration': ['src/agents/**/*.js', 'src/core/**/*.js']
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
if (categoryPatterns[category]) {
|
|
376
|
+
patterns.push(...categoryPatterns[category]);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Content-based patterns
|
|
380
|
+
if (content.includes('Lambda') || content.includes('handler')) {
|
|
381
|
+
patterns.push('src/handlers/**/*.js');
|
|
382
|
+
}
|
|
383
|
+
if (content.includes('React') || content.includes('component')) {
|
|
384
|
+
patterns.push('src/frontend/**/*.tsx', 'src/frontend/**/*.jsx');
|
|
385
|
+
}
|
|
386
|
+
if (content.includes('PostgreSQL') || content.includes('database')) {
|
|
387
|
+
patterns.push('**/*dbOperations.js', '**/*.sql');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return [...new Set(patterns)]; // Remove duplicates
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Extract code blocks from a section
|
|
395
|
+
*/
|
|
396
|
+
extractCodeBlocksFromSection(section) {
|
|
397
|
+
const blocks = [];
|
|
398
|
+
const codeBlocks = section.match(/```(\w+)?\n([\s\S]+?)```/g);
|
|
399
|
+
|
|
400
|
+
if (codeBlocks) {
|
|
401
|
+
for (const block of codeBlocks) {
|
|
402
|
+
const match = block.match(/```(\w+)?\n([\s\S]+?)```/);
|
|
403
|
+
if (match) {
|
|
404
|
+
// Look for description before code block
|
|
405
|
+
const beforeBlock = section.substring(0, section.indexOf(block));
|
|
406
|
+
const descMatch = beforeBlock.match(/([^\n]+)\n*$/);
|
|
407
|
+
|
|
408
|
+
blocks.push({
|
|
409
|
+
description: descMatch ? descMatch[1].trim() : null,
|
|
410
|
+
code: match[2].trim(),
|
|
411
|
+
language: match[1] || 'javascript'
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return blocks;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Extract element name from a rule statement
|
|
422
|
+
*/
|
|
423
|
+
extractElement(statement) {
|
|
424
|
+
// Remove modifiers
|
|
425
|
+
let cleaned = statement.replace(/^(ALWAYS|NEVER|DO NOT|DON'T)\s+/i, '');
|
|
426
|
+
|
|
427
|
+
// Extract first noun phrase (simple heuristic)
|
|
428
|
+
const match = cleaned.match(/^([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/);
|
|
429
|
+
if (match) {
|
|
430
|
+
return match[1];
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Extract from "Use X" or "Implement X"
|
|
434
|
+
const actionMatch = cleaned.match(/^(?:Use|Implement|Apply|Follow|Add|Create)\s+([^,.\n]+)/i);
|
|
435
|
+
if (actionMatch) {
|
|
436
|
+
return actionMatch[1].trim();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Fallback: first few words
|
|
440
|
+
const words = cleaned.split(/\s+/).slice(0, 3);
|
|
441
|
+
return words.join(' ');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Extract first paragraph from content
|
|
446
|
+
*/
|
|
447
|
+
extractFirstParagraph(content) {
|
|
448
|
+
const paragraphs = content.split(/\n\n+/);
|
|
449
|
+
for (const para of paragraphs) {
|
|
450
|
+
const cleaned = para.replace(/^#+\s+/, '').trim();
|
|
451
|
+
if (cleaned.length > 20 && !cleaned.startsWith('**')) {
|
|
452
|
+
return cleaned;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return 'Standard pattern';
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Generate unique pattern ID
|
|
460
|
+
*/
|
|
461
|
+
generatePatternId(category, filename, element) {
|
|
462
|
+
const categorySlug = category.replace(/[^a-z0-9]/gi, '_');
|
|
463
|
+
const fileSlug = filename.replace(/[^a-z0-9]/gi, '_');
|
|
464
|
+
const elementSlug = element.replace(/[^a-z0-9]/gi, '_').toLowerCase().substring(0, 30);
|
|
465
|
+
|
|
466
|
+
return `${categorySlug}_${fileSlug}_${elementSlug}`.toLowerCase();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Store patterns in database
|
|
471
|
+
*/
|
|
472
|
+
async seedPatternDatabase(patterns) {
|
|
473
|
+
if (patterns.length === 0) {
|
|
474
|
+
console.log('[StandardsIngestion] No patterns to ingest');
|
|
475
|
+
return 0;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
console.log(`[StandardsIngestion] Storing ${patterns.length} patterns in database...`);
|
|
479
|
+
|
|
480
|
+
let successCount = 0;
|
|
481
|
+
|
|
482
|
+
for (const pattern of patterns) {
|
|
483
|
+
try {
|
|
484
|
+
await executeQuery(`
|
|
485
|
+
INSERT INTO rapport.standards_patterns (
|
|
486
|
+
pattern_id,
|
|
487
|
+
element,
|
|
488
|
+
rule,
|
|
489
|
+
correlation,
|
|
490
|
+
source,
|
|
491
|
+
maturity,
|
|
492
|
+
scope,
|
|
493
|
+
category,
|
|
494
|
+
applicable_files,
|
|
495
|
+
anti_patterns,
|
|
496
|
+
examples,
|
|
497
|
+
cost_impact,
|
|
498
|
+
created_at,
|
|
499
|
+
last_updated
|
|
500
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NOW(), NOW())
|
|
501
|
+
ON CONFLICT (pattern_id)
|
|
502
|
+
DO UPDATE SET
|
|
503
|
+
element = EXCLUDED.element,
|
|
504
|
+
rule = EXCLUDED.rule,
|
|
505
|
+
correlation = EXCLUDED.correlation,
|
|
506
|
+
maturity = EXCLUDED.maturity,
|
|
507
|
+
scope = EXCLUDED.scope,
|
|
508
|
+
category = EXCLUDED.category,
|
|
509
|
+
applicable_files = EXCLUDED.applicable_files,
|
|
510
|
+
anti_patterns = EXCLUDED.anti_patterns,
|
|
511
|
+
examples = EXCLUDED.examples,
|
|
512
|
+
cost_impact = EXCLUDED.cost_impact,
|
|
513
|
+
last_updated = NOW()
|
|
514
|
+
`, [
|
|
515
|
+
pattern.pattern_id,
|
|
516
|
+
pattern.element,
|
|
517
|
+
pattern.rule,
|
|
518
|
+
pattern.correlation,
|
|
519
|
+
pattern.source,
|
|
520
|
+
pattern.maturity,
|
|
521
|
+
pattern.scope,
|
|
522
|
+
pattern.category,
|
|
523
|
+
pattern.applicable_files,
|
|
524
|
+
JSON.stringify(pattern.anti_patterns),
|
|
525
|
+
JSON.stringify(pattern.examples),
|
|
526
|
+
pattern.cost_impact ? JSON.stringify(pattern.cost_impact) : null
|
|
527
|
+
]);
|
|
528
|
+
|
|
529
|
+
successCount++;
|
|
530
|
+
|
|
531
|
+
if (this.verbose && successCount % 10 === 0) {
|
|
532
|
+
console.log(` Stored ${successCount}/${patterns.length} patterns...`);
|
|
533
|
+
}
|
|
534
|
+
} catch (error) {
|
|
535
|
+
console.error(` ✗ Failed to store pattern ${pattern.pattern_id}:`, error.message);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
console.log(`[StandardsIngestion] ✓ Successfully stored ${successCount} patterns`);
|
|
540
|
+
return successCount;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Check if standards need re-ingestion (for performance)
|
|
545
|
+
*/
|
|
546
|
+
async needsIngestion() {
|
|
547
|
+
try {
|
|
548
|
+
const result = await executeQuery(`
|
|
549
|
+
SELECT COUNT(*) as count, MAX(last_updated) as last_updated
|
|
550
|
+
FROM rapport.standards_patterns
|
|
551
|
+
WHERE source = 'equilateral-standards'
|
|
552
|
+
`);
|
|
553
|
+
|
|
554
|
+
const row = result.rows[0];
|
|
555
|
+
|
|
556
|
+
// Re-ingest if no patterns exist or haven't been updated in 7 days
|
|
557
|
+
if (row.count === '0') {
|
|
558
|
+
return { needed: true, reason: 'No standards ingested yet' };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const lastUpdated = new Date(row.last_updated);
|
|
562
|
+
const daysSince = (Date.now() - lastUpdated.getTime()) / (1000 * 60 * 60 * 24);
|
|
563
|
+
|
|
564
|
+
if (daysSince > 7) {
|
|
565
|
+
return { needed: true, reason: `Last updated ${daysSince.toFixed(0)} days ago` };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return { needed: false, count: row.count };
|
|
569
|
+
} catch (error) {
|
|
570
|
+
return { needed: true, reason: 'Database check failed' };
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
module.exports = { StandardsIngestion };
|