@equilateral_ai/mindmeld 3.5.3 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hooks/session-start.js +312 -85
- package/package.json +21 -13
- package/scripts/init-project.js +9 -23
- package/scripts/repo-analyzer.js +118 -2
- package/src/client/dbShim.js +16 -0
- package/src/core/AuthManager.js +3 -2
- package/src/handlers/helpers/dbOperations.js +9 -46
- package/src/index.js +2 -217
- package/src/utils/piiMask.js +16 -0
- package/scripts/inject.js +0 -409
- package/scripts/mcp-bridge.js +0 -220
- package/scripts/standards.js +0 -285
- package/src/collaboration/CollaborationPrompt.js +0 -460
- package/src/core/AlertEngine.js +0 -813
- package/src/core/AlertNotifier.js +0 -363
- package/src/core/CorrelationAnalyzer.js +0 -931
- package/src/core/CrossReferenceEngine.js +0 -624
- package/src/core/CurationEngine.js +0 -688
- package/src/core/DeprecationScheduler.js +0 -183
- package/src/core/LoadBearingDetector.js +0 -242
- package/src/core/NotificationService.js +0 -1032
- package/src/core/RapportOrchestrator.js +0 -632
- package/src/core/RelevanceDetector.js +0 -694
- package/src/core/StandardLifecycle.js +0 -244
- package/src/core/StandardsIngestion.js +0 -991
- package/src/core/TeamLoadBearingDetector.js +0 -431
- package/src/core/parsers/adrParser.js +0 -479
- package/src/core/parsers/cursorRulesParser.js +0 -564
- package/src/core/parsers/eslintParser.js +0 -439
- package/src/database/dbOperations.js +0 -105
- package/src/handlers/activity/activityGetMe.js +0 -98
- package/src/handlers/activity/activityGetTeam.js +0 -175
- package/src/handlers/admin/adminSetup.js +0 -216
- package/src/handlers/alerts/alertsAcknowledge.js +0 -92
- package/src/handlers/alerts/alertsGet.js +0 -250
- package/src/handlers/analytics/activitySummaryGet.js +0 -234
- package/src/handlers/analytics/coachingGet.js +0 -361
- package/src/handlers/analytics/convergenceGet.js +0 -236
- package/src/handlers/analytics/developerScoreGet.js +0 -137
- package/src/handlers/collaborators/collaboratorAdd.js +0 -200
- package/src/handlers/collaborators/collaboratorInvite.js +0 -219
- package/src/handlers/collaborators/collaboratorList.js +0 -82
- package/src/handlers/collaborators/collaboratorRemove.js +0 -128
- package/src/handlers/collaborators/inviteAccept.js +0 -122
- package/src/handlers/company/companyUsersDelete.js +0 -141
- package/src/handlers/company/companyUsersGet.js +0 -90
- package/src/handlers/company/companyUsersPost.js +0 -267
- package/src/handlers/company/companyUsersPut.js +0 -76
- package/src/handlers/context/contextGet.js +0 -57
- package/src/handlers/context/invariantsGet.js +0 -74
- package/src/handlers/context/loopsGet.js +0 -82
- package/src/handlers/context/notesCreate.js +0 -74
- package/src/handlers/context/purposeGet.js +0 -78
- package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
- package/src/handlers/correlations/correlationsGet.js +0 -93
- package/src/handlers/correlations/correlationsProjectGet.js +0 -153
- package/src/handlers/enterprise/controlTowerGet.js +0 -224
- package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
- package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
- package/src/handlers/github/githubConnectionStatus.js +0 -49
- package/src/handlers/github/githubDiscoverPatterns.js +0 -621
- package/src/handlers/github/githubOAuthCallback.js +0 -178
- package/src/handlers/github/githubOAuthStart.js +0 -59
- package/src/handlers/github/githubPatternsReview.js +0 -76
- package/src/handlers/github/githubReposList.js +0 -105
- package/src/handlers/health/healthGet.js +0 -55
- package/src/handlers/helpers/auditLogger.js +0 -201
- package/src/handlers/helpers/checkSuperAdmin.js +0 -84
- package/src/handlers/helpers/decisionFrames.js +0 -29
- package/src/handlers/helpers/errorHandler.js +0 -49
- package/src/handlers/helpers/index.js +0 -138
- package/src/handlers/helpers/lambdaWrapper.js +0 -60
- package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
- package/src/handlers/helpers/predictiveCache.js +0 -51
- package/src/handlers/helpers/projectAccess.js +0 -88
- package/src/handlers/helpers/responseUtil.js +0 -55
- package/src/handlers/helpers/subscriptionTiers.js +0 -1168
- package/src/handlers/mcp/mcpHandler.js +0 -569
- package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
- package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
- package/src/handlers/notifications/getPreferences.js +0 -84
- package/src/handlers/notifications/sendNotification.js +0 -170
- package/src/handlers/notifications/updatePreferences.js +0 -316
- package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
- package/src/handlers/patterns/patternUsagePost.js +0 -182
- package/src/handlers/patterns/patternViolationPost.js +0 -185
- package/src/handlers/projects/projectCreate.js +0 -248
- package/src/handlers/projects/projectDelete.js +0 -82
- package/src/handlers/projects/projectGet.js +0 -95
- package/src/handlers/projects/projectUpdate.js +0 -117
- package/src/handlers/reports/aiLeverage.js +0 -210
- package/src/handlers/reports/engineeringInvestment.js +0 -132
- package/src/handlers/reports/riskForecast.js +0 -206
- package/src/handlers/reports/standardsRoi.js +0 -254
- package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
- package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
- package/src/handlers/scheduled/generateAlerts.js +0 -135
- package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
- package/src/handlers/scheduled/refreshActivity.js +0 -21
- package/src/handlers/scheduled/scanCompliance.js +0 -334
- package/src/handlers/sessions/sessionEndPost.js +0 -180
- package/src/handlers/sessions/sessionStandardsPost.js +0 -171
- package/src/handlers/standards/catalogGet.js +0 -185
- package/src/handlers/standards/catalogSync.js +0 -120
- package/src/handlers/standards/discoveriesGet.js +0 -89
- package/src/handlers/standards/projectStandardsGet.js +0 -129
- package/src/handlers/standards/projectStandardsPut.js +0 -151
- package/src/handlers/standards/standardsAuditGet.js +0 -65
- package/src/handlers/standards/standardsParseUpload.js +0 -149
- package/src/handlers/standards/standardsRelevantPost.js +0 -405
- package/src/handlers/standards/standardsTransition.js +0 -161
- package/src/handlers/stripe/addonManagePost.js +0 -240
- package/src/handlers/stripe/billingPortalPost.js +0 -93
- package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
- package/src/handlers/stripe/seatsUpdatePost.js +0 -185
- package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
- package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
- package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
- package/src/handlers/stripe/webhookPost.js +0 -482
- package/src/handlers/user/apiTokenCreate.js +0 -71
- package/src/handlers/user/apiTokenList.js +0 -64
- package/src/handlers/user/userSplashAck.js +0 -91
- package/src/handlers/user/userSplashGet.js +0 -211
- package/src/handlers/users/cognitoPostConfirmation.js +0 -186
- package/src/handlers/users/cognitoPreSignUp.js +0 -114
- package/src/handlers/users/userEntitlementsGet.js +0 -89
- package/src/handlers/users/userGet.js +0 -118
- package/src/handlers/users/userProfilePut.js +0 -77
- package/src/handlers/webhooks/githubWebhook.js +0 -215
|
@@ -1,991 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StandardsIngestion.js - Phase 1 of Rapport Standards Integration
|
|
3
|
-
*
|
|
4
|
-
* Parses and ingests .equilateral-standards/ YAML files into Rapport database
|
|
5
|
-
* Creates standards_patterns from rules, anti-patterns, examples, and cost impacts
|
|
6
|
-
*
|
|
7
|
-
* YAML Schema (equilateral-standards-yaml):
|
|
8
|
-
* id, category, priority (10/20/30), updated
|
|
9
|
-
* rules: [{ action: ALWAYS|NEVER|USE|PREFER|AVOID, rule: string, applies_to: [globs] }]
|
|
10
|
-
* anti_patterns: [strings]
|
|
11
|
-
* examples: { name: code }
|
|
12
|
-
* cost_impact: { pattern: impact }
|
|
13
|
-
* tags: [strings]
|
|
14
|
-
*
|
|
15
|
-
* Reference: /Users/jamesford/Source/rapport/docs/RAPPORT_STANDARDS_INTEGRATION_DESIGN.md
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const fs = require('fs').promises;
|
|
19
|
-
const path = require('path');
|
|
20
|
-
const yaml = require('js-yaml');
|
|
21
|
-
const { executeQuery } = require('../handlers/helpers/dbOperations');
|
|
22
|
-
|
|
23
|
-
class StandardsIngestion {
|
|
24
|
-
constructor(options = {}) {
|
|
25
|
-
this.standardsPath = options.standardsPath || path.join(process.cwd(), '.equilateral-standards');
|
|
26
|
-
this.verbose = options.verbose || false;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Main ingestion entry point
|
|
31
|
-
* Discovers and parses all .equilateral-standards/ YAML files (with markdown fallback)
|
|
32
|
-
*/
|
|
33
|
-
async ingestEquilateralStandards() {
|
|
34
|
-
console.log(`[StandardsIngestion] Starting ingestion from ${this.standardsPath}`);
|
|
35
|
-
|
|
36
|
-
const startTime = Date.now();
|
|
37
|
-
const patterns = [];
|
|
38
|
-
|
|
39
|
-
// Define standard categories
|
|
40
|
-
const categories = [
|
|
41
|
-
'serverless-saas-aws',
|
|
42
|
-
'multi-agent-orchestration',
|
|
43
|
-
'compliance-security',
|
|
44
|
-
'database',
|
|
45
|
-
'frontend-development',
|
|
46
|
-
'cost-optimization',
|
|
47
|
-
'backend',
|
|
48
|
-
'deployment',
|
|
49
|
-
'real-time-systems',
|
|
50
|
-
'testing',
|
|
51
|
-
'well-architected'
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
// Discover and parse all standards files
|
|
55
|
-
for (const category of categories) {
|
|
56
|
-
const categoryPath = path.join(this.standardsPath, category);
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const files = await this.discoverStandardFiles(categoryPath);
|
|
60
|
-
|
|
61
|
-
for (const file of files) {
|
|
62
|
-
try {
|
|
63
|
-
const standard = await this.parseStandard(file, category);
|
|
64
|
-
|
|
65
|
-
if (standard && standard.patterns.length > 0) {
|
|
66
|
-
patterns.push(...standard.patterns);
|
|
67
|
-
|
|
68
|
-
if (this.verbose) {
|
|
69
|
-
console.log(` ✓ Parsed ${path.basename(file)}: ${standard.patterns.length} patterns`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
} catch (parseError) {
|
|
73
|
-
console.warn(` ⚠ Failed to parse ${path.basename(file)}:`, parseError.message);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
} catch (categoryError) {
|
|
77
|
-
if (categoryError.code !== 'ENOENT') {
|
|
78
|
-
console.warn(` ⚠ Failed to process category ${category}:`, categoryError.message);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Store in database
|
|
84
|
-
const ingestedCount = await this.seedPatternDatabase(patterns);
|
|
85
|
-
|
|
86
|
-
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
patternsIngested: ingestedCount,
|
|
90
|
-
totalPatternsFound: patterns.length,
|
|
91
|
-
categories: categories.length,
|
|
92
|
-
timestamp: new Date().toISOString(),
|
|
93
|
-
duration: `${duration}s`
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Parse all standards from YAML files without storing in database.
|
|
99
|
-
* Used as a fallback when the database is unavailable (e.g., local dev).
|
|
100
|
-
* @returns {Promise<Array>} Parsed patterns array
|
|
101
|
-
*/
|
|
102
|
-
async parseAllStandards() {
|
|
103
|
-
const patterns = [];
|
|
104
|
-
|
|
105
|
-
const categories = [
|
|
106
|
-
'serverless-saas-aws',
|
|
107
|
-
'multi-agent-orchestration',
|
|
108
|
-
'compliance-security',
|
|
109
|
-
'database',
|
|
110
|
-
'frontend-development',
|
|
111
|
-
'cost-optimization',
|
|
112
|
-
'backend',
|
|
113
|
-
'deployment',
|
|
114
|
-
'real-time-systems',
|
|
115
|
-
'testing',
|
|
116
|
-
'well-architected'
|
|
117
|
-
];
|
|
118
|
-
|
|
119
|
-
for (const category of categories) {
|
|
120
|
-
const categoryPath = path.join(this.standardsPath, category);
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const files = await this.discoverStandardFiles(categoryPath);
|
|
124
|
-
|
|
125
|
-
for (const file of files) {
|
|
126
|
-
try {
|
|
127
|
-
const standard = await this.parseStandard(file, category);
|
|
128
|
-
if (standard && standard.patterns.length > 0) {
|
|
129
|
-
patterns.push(...standard.patterns);
|
|
130
|
-
}
|
|
131
|
-
} catch (parseError) {
|
|
132
|
-
// Skip unparseable files
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
} catch (categoryError) {
|
|
136
|
-
if (categoryError.code !== 'ENOENT') {
|
|
137
|
-
// Skip missing categories silently
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return patterns;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Discover all standard files in a category directory
|
|
147
|
-
* Prefers YAML files over markdown - if both exist for the same base name, only YAML is returned
|
|
148
|
-
*/
|
|
149
|
-
async discoverStandardFiles(categoryPath) {
|
|
150
|
-
try {
|
|
151
|
-
const entries = await fs.readdir(categoryPath, { withFileTypes: true });
|
|
152
|
-
const fileMap = new Map(); // baseName -> { yaml: path, md: path }
|
|
153
|
-
|
|
154
|
-
for (const entry of entries) {
|
|
155
|
-
const fullPath = path.join(categoryPath, entry.name);
|
|
156
|
-
|
|
157
|
-
if (entry.isDirectory()) {
|
|
158
|
-
// Recursively search subdirectories
|
|
159
|
-
const subFiles = await this.discoverStandardFiles(fullPath);
|
|
160
|
-
// Add subdirectory files directly (they've already been deduplicated)
|
|
161
|
-
for (const subFile of subFiles) {
|
|
162
|
-
const subBaseName = path.basename(subFile).replace(/\.(yaml|yml|md)$/, '');
|
|
163
|
-
const subKey = `${path.dirname(subFile)}/${subBaseName}`;
|
|
164
|
-
if (!fileMap.has(subKey)) {
|
|
165
|
-
fileMap.set(subKey, {});
|
|
166
|
-
}
|
|
167
|
-
if (subFile.endsWith('.yaml') || subFile.endsWith('.yml')) {
|
|
168
|
-
fileMap.get(subKey).yaml = subFile;
|
|
169
|
-
} else if (subFile.endsWith('.md')) {
|
|
170
|
-
fileMap.get(subKey).md = subFile;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
} else if (entry.isFile()) {
|
|
174
|
-
const isYaml = entry.name.endsWith('.yaml') || entry.name.endsWith('.yml');
|
|
175
|
-
const isMd = entry.name.endsWith('.md');
|
|
176
|
-
|
|
177
|
-
if (isYaml || isMd) {
|
|
178
|
-
const baseName = entry.name.replace(/\.(yaml|yml|md)$/, '');
|
|
179
|
-
const key = `${categoryPath}/${baseName}`;
|
|
180
|
-
|
|
181
|
-
if (!fileMap.has(key)) {
|
|
182
|
-
fileMap.set(key, {});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (isYaml) {
|
|
186
|
-
fileMap.get(key).yaml = fullPath;
|
|
187
|
-
} else {
|
|
188
|
-
fileMap.get(key).md = fullPath;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Build final list, preferring YAML over markdown
|
|
195
|
-
const files = [];
|
|
196
|
-
for (const paths of fileMap.values()) {
|
|
197
|
-
if (paths.yaml) {
|
|
198
|
-
files.push(paths.yaml);
|
|
199
|
-
} else if (paths.md) {
|
|
200
|
-
files.push(paths.md);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return files;
|
|
205
|
-
} catch (error) {
|
|
206
|
-
if (error.code === 'ENOENT') {
|
|
207
|
-
// Category doesn't exist - not an error
|
|
208
|
-
return [];
|
|
209
|
-
}
|
|
210
|
-
throw error;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Parse a standards file into Rapport pattern format
|
|
216
|
-
* Detects file type and routes to appropriate parser
|
|
217
|
-
*/
|
|
218
|
-
async parseStandard(filepath, category) {
|
|
219
|
-
const isYaml = filepath.endsWith('.yaml') || filepath.endsWith('.yml');
|
|
220
|
-
|
|
221
|
-
if (isYaml) {
|
|
222
|
-
return this.parseYamlStandard(filepath, category);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return this.parseMarkdownStandard(filepath, category);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Parse a YAML standards file into Rapport pattern format
|
|
230
|
-
* Supports both type: standard (v2.0) and type: workflow (v3.0)
|
|
231
|
-
*
|
|
232
|
-
* YAML format (standard):
|
|
233
|
-
* id, category, priority, rules[], anti_patterns[]
|
|
234
|
-
*
|
|
235
|
-
* YAML format (workflow):
|
|
236
|
-
* id, category, priority, type: workflow, trigger, steps[], preconditions[], anti_patterns[]
|
|
237
|
-
*/
|
|
238
|
-
async parseYamlStandard(filepath, category) {
|
|
239
|
-
const content = await fs.readFile(filepath, 'utf-8');
|
|
240
|
-
const ext = filepath.endsWith('.yml') ? '.yml' : '.yaml';
|
|
241
|
-
const filename = path.basename(filepath, ext);
|
|
242
|
-
|
|
243
|
-
let doc;
|
|
244
|
-
try {
|
|
245
|
-
doc = yaml.load(content);
|
|
246
|
-
} catch (parseError) {
|
|
247
|
-
throw new Error(`YAML parse error: ${parseError.message}`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (!doc || typeof doc !== 'object') {
|
|
251
|
-
throw new Error('Invalid YAML structure: expected an object');
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Route to workflow parser if type: workflow
|
|
255
|
-
if (doc.type === 'workflow') {
|
|
256
|
-
return this.parseYamlWorkflow(doc, filepath, filename, category);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const patterns = [];
|
|
260
|
-
const standardId = doc.id || filename;
|
|
261
|
-
const standardCategory = doc.category || category;
|
|
262
|
-
const standardPriority = doc.priority || 30;
|
|
263
|
-
|
|
264
|
-
// Extract anti-patterns as simple strings
|
|
265
|
-
const antiPatterns = Array.isArray(doc.anti_patterns)
|
|
266
|
-
? doc.anti_patterns.map(ap => ({
|
|
267
|
-
description: typeof ap === 'string' ? ap : ap.description || 'Anti-pattern to avoid',
|
|
268
|
-
code: typeof ap === 'object' ? ap.code : null,
|
|
269
|
-
language: typeof ap === 'object' ? ap.language : null
|
|
270
|
-
}))
|
|
271
|
-
: [];
|
|
272
|
-
|
|
273
|
-
// Extract examples - handles both array of strings and named object formats
|
|
274
|
-
const examples = [];
|
|
275
|
-
if (doc.examples) {
|
|
276
|
-
if (Array.isArray(doc.examples)) {
|
|
277
|
-
// Array format: ["code example 1", "code example 2"]
|
|
278
|
-
for (const example of doc.examples) {
|
|
279
|
-
if (typeof example === 'string') {
|
|
280
|
-
examples.push({
|
|
281
|
-
description: 'Example',
|
|
282
|
-
code: example.trim(),
|
|
283
|
-
language: this.inferLanguageFromCode(example)
|
|
284
|
-
});
|
|
285
|
-
} else if (typeof example === 'object' && example !== null) {
|
|
286
|
-
// Object in array: { description: "...", code: "..." }
|
|
287
|
-
examples.push({
|
|
288
|
-
description: example.description || 'Example',
|
|
289
|
-
code: (example.code || '').trim(),
|
|
290
|
-
language: example.language || this.inferLanguageFromCode(example.code || '')
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
} else if (typeof doc.examples === 'object') {
|
|
295
|
-
// Named object format: { example_name: "code here" }
|
|
296
|
-
for (const [name, code] of Object.entries(doc.examples)) {
|
|
297
|
-
examples.push({
|
|
298
|
-
description: name.replace(/_/g, ' '),
|
|
299
|
-
code: typeof code === 'string' ? code.trim() : String(code),
|
|
300
|
-
language: this.inferLanguageFromCode(code)
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Extract cost_impact - now a structured object in YAML format
|
|
307
|
-
const costImpact = doc.cost_impact && typeof doc.cost_impact === 'object'
|
|
308
|
-
? doc.cost_impact
|
|
309
|
-
: (typeof doc.cost_impact === 'string' ? { description: doc.cost_impact } : null);
|
|
310
|
-
|
|
311
|
-
// Extract tags for metadata
|
|
312
|
-
const tags = Array.isArray(doc.tags) ? doc.tags : [];
|
|
313
|
-
|
|
314
|
-
// Process each rule
|
|
315
|
-
const rules = Array.isArray(doc.rules) ? doc.rules : [];
|
|
316
|
-
|
|
317
|
-
for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {
|
|
318
|
-
const rule = rules[ruleIndex];
|
|
319
|
-
if (!rule || typeof rule !== 'object') continue;
|
|
320
|
-
|
|
321
|
-
const action = rule.action || 'USE';
|
|
322
|
-
const ruleText = rule.rule || '';
|
|
323
|
-
const appliesTo = Array.isArray(rule.applies_to) ? rule.applies_to : [];
|
|
324
|
-
|
|
325
|
-
// Construct element name from action and first few words
|
|
326
|
-
const element = this.extractElement(`${action} ${ruleText}`);
|
|
327
|
-
|
|
328
|
-
// Construct full rule statement
|
|
329
|
-
const fullRule = `${action}: ${ruleText}`;
|
|
330
|
-
|
|
331
|
-
const patternId = this.generatePatternId(standardCategory, standardId, element, ruleIndex);
|
|
332
|
-
|
|
333
|
-
// Map action to maturity
|
|
334
|
-
const actionMaturityMap = {
|
|
335
|
-
'ALWAYS': 'enforced',
|
|
336
|
-
'NEVER': 'enforced',
|
|
337
|
-
'USE': 'validated',
|
|
338
|
-
'PREFER': 'validated',
|
|
339
|
-
'AVOID': 'validated'
|
|
340
|
-
};
|
|
341
|
-
const maturity = actionMaturityMap[action] || 'validated';
|
|
342
|
-
|
|
343
|
-
patterns.push({
|
|
344
|
-
pattern_id: patternId,
|
|
345
|
-
file_name: filename,
|
|
346
|
-
element: element,
|
|
347
|
-
rule: fullRule,
|
|
348
|
-
priority: standardPriority,
|
|
349
|
-
correlation: 1.0,
|
|
350
|
-
source: 'equilateral-standards',
|
|
351
|
-
maturity: maturity,
|
|
352
|
-
scope: 'organization',
|
|
353
|
-
category: standardCategory,
|
|
354
|
-
applicable_files: appliesTo.length > 0 ? appliesTo : this.extractApplicableFiles('', standardCategory),
|
|
355
|
-
anti_patterns: antiPatterns,
|
|
356
|
-
examples: examples,
|
|
357
|
-
cost_impact: costImpact,
|
|
358
|
-
tags: tags,
|
|
359
|
-
source_file: filepath,
|
|
360
|
-
title: standardId
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// If no rules found but document exists, create a single pattern from the document
|
|
365
|
-
if (patterns.length === 0 && doc.id) {
|
|
366
|
-
const patternId = this.generatePatternId(standardCategory, standardId, 'standard_pattern');
|
|
367
|
-
|
|
368
|
-
patterns.push({
|
|
369
|
-
pattern_id: patternId,
|
|
370
|
-
file_name: filename,
|
|
371
|
-
element: 'Standard Pattern',
|
|
372
|
-
rule: `Standard: ${standardId}`,
|
|
373
|
-
priority: standardPriority,
|
|
374
|
-
correlation: 1.0,
|
|
375
|
-
source: 'equilateral-standards',
|
|
376
|
-
maturity: 'validated',
|
|
377
|
-
scope: 'organization',
|
|
378
|
-
category: standardCategory,
|
|
379
|
-
applicable_files: this.extractApplicableFiles('', standardCategory),
|
|
380
|
-
anti_patterns: antiPatterns,
|
|
381
|
-
examples: examples,
|
|
382
|
-
cost_impact: costImpact,
|
|
383
|
-
tags: tags,
|
|
384
|
-
source_file: filepath,
|
|
385
|
-
title: standardId
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
return { patterns };
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Parse a workflow YAML file into a single Rapport pattern with workflow metadata
|
|
394
|
-
* Workflows are stored as one pattern (not exploded into per-rule patterns)
|
|
395
|
-
* so the injection system can render them as ordered procedures.
|
|
396
|
-
*/
|
|
397
|
-
parseYamlWorkflow(doc, filepath, filename, category) {
|
|
398
|
-
const standardId = doc.id || filename;
|
|
399
|
-
const standardCategory = doc.category || category;
|
|
400
|
-
const standardPriority = doc.priority || 10; // Workflows default to high priority
|
|
401
|
-
|
|
402
|
-
const antiPatterns = Array.isArray(doc.anti_patterns)
|
|
403
|
-
? doc.anti_patterns.map(ap => ({
|
|
404
|
-
description: typeof ap === 'string' ? ap : ap.description || 'Anti-pattern to avoid',
|
|
405
|
-
code: typeof ap === 'object' ? ap.code : null,
|
|
406
|
-
language: typeof ap === 'object' ? ap.language : null
|
|
407
|
-
}))
|
|
408
|
-
: [];
|
|
409
|
-
|
|
410
|
-
const tags = Array.isArray(doc.tags) ? doc.tags : [];
|
|
411
|
-
const costImpact = doc.cost_impact && typeof doc.cost_impact === 'object'
|
|
412
|
-
? doc.cost_impact
|
|
413
|
-
: (typeof doc.cost_impact === 'string' ? { description: doc.cost_impact } : null);
|
|
414
|
-
|
|
415
|
-
const steps = Array.isArray(doc.steps) ? doc.steps : [];
|
|
416
|
-
const preconditions = Array.isArray(doc.preconditions) ? doc.preconditions : [];
|
|
417
|
-
const trigger = doc.trigger || '';
|
|
418
|
-
const related = Array.isArray(doc.related) ? doc.related : [];
|
|
419
|
-
|
|
420
|
-
const patternId = this.generatePatternId(standardCategory, standardId, 'workflow');
|
|
421
|
-
|
|
422
|
-
// Build a readable element name from the id
|
|
423
|
-
const element = standardId.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
424
|
-
|
|
425
|
-
// The rule field contains the trigger for search/matching
|
|
426
|
-
const rule = `WORKFLOW: ${trigger}`;
|
|
427
|
-
|
|
428
|
-
// Store workflow structure in examples field (JSON-serializable)
|
|
429
|
-
// This preserves the full workflow for the injection formatter
|
|
430
|
-
const workflowData = {
|
|
431
|
-
type: 'workflow',
|
|
432
|
-
trigger: trigger,
|
|
433
|
-
preconditions: preconditions,
|
|
434
|
-
steps: steps.map((step, i) => ({
|
|
435
|
-
index: i + 1,
|
|
436
|
-
name: step.name || `Step ${i + 1}`,
|
|
437
|
-
description: step.description || '',
|
|
438
|
-
command: step.command || null,
|
|
439
|
-
validation: step.validation || null,
|
|
440
|
-
standards: Array.isArray(step.standards) ? step.standards : [],
|
|
441
|
-
gate: step.gate === true
|
|
442
|
-
})),
|
|
443
|
-
related: related
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
return {
|
|
447
|
-
patterns: [{
|
|
448
|
-
pattern_id: patternId,
|
|
449
|
-
file_name: filename,
|
|
450
|
-
element: element,
|
|
451
|
-
rule: rule,
|
|
452
|
-
priority: standardPriority,
|
|
453
|
-
correlation: 1.0,
|
|
454
|
-
source: 'equilateral-standards',
|
|
455
|
-
maturity: 'enforced',
|
|
456
|
-
scope: 'organization',
|
|
457
|
-
category: standardCategory,
|
|
458
|
-
applicable_files: this.extractApplicableFiles('', standardCategory),
|
|
459
|
-
anti_patterns: antiPatterns,
|
|
460
|
-
examples: [workflowData], // Workflow data stored as first example
|
|
461
|
-
cost_impact: costImpact,
|
|
462
|
-
tags: [...tags, 'workflow'],
|
|
463
|
-
source_file: filepath,
|
|
464
|
-
title: standardId,
|
|
465
|
-
type: 'workflow' // Flag for injection formatter
|
|
466
|
-
}]
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Parse a markdown standards file into Rapport pattern format
|
|
472
|
-
*/
|
|
473
|
-
async parseMarkdownStandard(filepath, category) {
|
|
474
|
-
const content = await fs.readFile(filepath, 'utf-8');
|
|
475
|
-
const filename = path.basename(filepath, '.md');
|
|
476
|
-
|
|
477
|
-
// Extract standard metadata from markdown
|
|
478
|
-
const title = this.extractTitle(content);
|
|
479
|
-
const enforcementRules = this.extractEnforcementRules(content);
|
|
480
|
-
const antiPatterns = this.extractAntiPatterns(content);
|
|
481
|
-
const examples = this.extractExamples(content);
|
|
482
|
-
const costImpact = this.extractCostImpact(content);
|
|
483
|
-
const applicableFiles = this.extractApplicableFiles(content, category);
|
|
484
|
-
|
|
485
|
-
const patterns = [];
|
|
486
|
-
|
|
487
|
-
// Create pattern for each enforcement rule
|
|
488
|
-
for (const rule of enforcementRules) {
|
|
489
|
-
const patternId = this.generatePatternId(category, filename, rule.element);
|
|
490
|
-
|
|
491
|
-
// Map severity to priority (10=high, 20=medium, 30=low per DB constraint)
|
|
492
|
-
const priorityMap = {
|
|
493
|
-
'CRITICAL': 10,
|
|
494
|
-
'MANDATORY': 10,
|
|
495
|
-
'ENFORCED': 20,
|
|
496
|
-
'VALIDATED': 30
|
|
497
|
-
};
|
|
498
|
-
const priority = priorityMap[rule.severity] || 30;
|
|
499
|
-
|
|
500
|
-
patterns.push({
|
|
501
|
-
pattern_id: patternId,
|
|
502
|
-
file_name: filename, // Source markdown file name (without .md)
|
|
503
|
-
element: rule.element,
|
|
504
|
-
rule: rule.rule,
|
|
505
|
-
priority: priority,
|
|
506
|
-
correlation: 1.0, // Standards are mandatory
|
|
507
|
-
source: 'equilateral-standards',
|
|
508
|
-
maturity: rule.severity === 'MANDATORY' || rule.severity === 'CRITICAL' ? 'enforced' : 'validated',
|
|
509
|
-
scope: 'organization',
|
|
510
|
-
category: category,
|
|
511
|
-
applicable_files: applicableFiles,
|
|
512
|
-
anti_patterns: antiPatterns,
|
|
513
|
-
examples: examples,
|
|
514
|
-
cost_impact: costImpact,
|
|
515
|
-
source_file: filepath,
|
|
516
|
-
title: title
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
return { patterns };
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Extract title from markdown (first H1)
|
|
525
|
-
*/
|
|
526
|
-
extractTitle(content) {
|
|
527
|
-
const match = content.match(/^#\s+(.+)$/m);
|
|
528
|
-
return match ? match[1].trim() : 'Untitled Standard';
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* Extract enforcement rules from markdown
|
|
533
|
-
* Looks for sections like "MANDATORY PATTERNS", "Core Principles", etc.
|
|
534
|
-
*/
|
|
535
|
-
extractEnforcementRules(content) {
|
|
536
|
-
const rules = [];
|
|
537
|
-
|
|
538
|
-
// Pattern 1: MANDATORY/CRITICAL sections
|
|
539
|
-
const mandatorySection = content.match(/##?\s*⚠?️?\s*(MANDATORY|CRITICAL)[^#]*/gi);
|
|
540
|
-
if (mandatorySection) {
|
|
541
|
-
for (const section of mandatorySection) {
|
|
542
|
-
const sectionRules = this.extractRulesFromSection(section, 'MANDATORY');
|
|
543
|
-
rules.push(...sectionRules);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Pattern 2: "ALWAYS" and "NEVER" statements
|
|
548
|
-
const alwaysNever = content.match(/(?:ALWAYS|NEVER)\s+([^.\n]+)/g);
|
|
549
|
-
if (alwaysNever) {
|
|
550
|
-
for (const statement of alwaysNever) {
|
|
551
|
-
rules.push({
|
|
552
|
-
element: this.extractElement(statement),
|
|
553
|
-
rule: statement.trim(),
|
|
554
|
-
severity: 'MANDATORY'
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Pattern 3: Core Principles
|
|
560
|
-
const principleMatch = content.match(/##?\s*Core Principle[s]?[:\s]+([^#]+)/i);
|
|
561
|
-
if (principleMatch) {
|
|
562
|
-
rules.push({
|
|
563
|
-
element: 'Core Principle',
|
|
564
|
-
rule: principleMatch[1].trim(),
|
|
565
|
-
severity: 'ENFORCED'
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
return rules.length > 0 ? rules : [{
|
|
570
|
-
element: 'Standard Pattern',
|
|
571
|
-
rule: this.extractFirstParagraph(content),
|
|
572
|
-
severity: 'VALIDATED'
|
|
573
|
-
}];
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Extract rules from a section of content
|
|
578
|
-
*/
|
|
579
|
-
extractRulesFromSection(section, severity) {
|
|
580
|
-
const rules = [];
|
|
581
|
-
|
|
582
|
-
// Look for numbered or bulleted lists
|
|
583
|
-
const listItems = section.match(/^[\s]*[-*\d]+\.?\s+(.+)$/gm);
|
|
584
|
-
if (listItems) {
|
|
585
|
-
for (const item of listItems) {
|
|
586
|
-
const cleaned = item.replace(/^[\s]*[-*\d]+\.?\s+/, '').trim();
|
|
587
|
-
if (cleaned.length > 10) { // Filter out very short items
|
|
588
|
-
rules.push({
|
|
589
|
-
element: this.extractElement(cleaned),
|
|
590
|
-
rule: cleaned,
|
|
591
|
-
severity: severity
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// Look for bold statements
|
|
598
|
-
const boldStatements = section.match(/\*\*(.+?)\*\*/g);
|
|
599
|
-
if (boldStatements && rules.length === 0) {
|
|
600
|
-
for (const statement of boldStatements) {
|
|
601
|
-
const cleaned = statement.replace(/\*\*/g, '').trim();
|
|
602
|
-
if (cleaned.length > 10 && !cleaned.match(/^(MANDATORY|CRITICAL|NOTE|IMPORTANT)/i)) {
|
|
603
|
-
rules.push({
|
|
604
|
-
element: this.extractElement(cleaned),
|
|
605
|
-
rule: cleaned,
|
|
606
|
-
severity: severity
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
return rules;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/**
|
|
616
|
-
* Extract anti-patterns from markdown
|
|
617
|
-
* Looks for ❌ WRONG, NEVER DO THIS, Anti-Pattern sections
|
|
618
|
-
*/
|
|
619
|
-
extractAntiPatterns(content) {
|
|
620
|
-
const antiPatterns = [];
|
|
621
|
-
|
|
622
|
-
// Pattern 1: ❌ markers
|
|
623
|
-
const wrongPatterns = content.match(/❌[^```]*?```[^`]+```/gs);
|
|
624
|
-
if (wrongPatterns) {
|
|
625
|
-
for (const pattern of wrongPatterns) {
|
|
626
|
-
const description = pattern.match(/❌\s*([^\n`]+)/);
|
|
627
|
-
const code = pattern.match(/```(\w+)?\n([\s\S]+?)```/);
|
|
628
|
-
|
|
629
|
-
if (description && code) {
|
|
630
|
-
antiPatterns.push({
|
|
631
|
-
description: description[1].trim(),
|
|
632
|
-
code: code[2].trim(),
|
|
633
|
-
language: code[1] || 'javascript'
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// Pattern 2: Anti-Pattern sections
|
|
640
|
-
const antiPatternSection = content.match(/##?\s*Anti-Pattern[s]?[^#]*/gi);
|
|
641
|
-
if (antiPatternSection) {
|
|
642
|
-
for (const section of antiPatternSection) {
|
|
643
|
-
const sectionPatterns = this.extractCodeBlocksFromSection(section);
|
|
644
|
-
antiPatterns.push(...sectionPatterns.map(p => ({
|
|
645
|
-
description: p.description || 'Anti-pattern to avoid',
|
|
646
|
-
code: p.code,
|
|
647
|
-
language: p.language
|
|
648
|
-
})));
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
return antiPatterns;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* Extract examples from markdown
|
|
657
|
-
* Looks for ✅ CORRECT, Example sections
|
|
658
|
-
*/
|
|
659
|
-
extractExamples(content) {
|
|
660
|
-
const examples = [];
|
|
661
|
-
|
|
662
|
-
// Pattern 1: ✅ markers
|
|
663
|
-
const correctPatterns = content.match(/✅[^```]*?```[^`]+```/gs);
|
|
664
|
-
if (correctPatterns) {
|
|
665
|
-
for (const pattern of correctPatterns) {
|
|
666
|
-
const description = pattern.match(/✅\s*([^\n`]+)/);
|
|
667
|
-
const code = pattern.match(/```(\w+)?\n([\s\S]+?)```/);
|
|
668
|
-
|
|
669
|
-
if (description && code) {
|
|
670
|
-
examples.push({
|
|
671
|
-
description: description[1].trim(),
|
|
672
|
-
code: code[2].trim(),
|
|
673
|
-
language: code[1] || 'javascript'
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// Pattern 2: Example sections
|
|
680
|
-
const exampleSection = content.match(/##?\s*Example[s]?[^#]*/gi);
|
|
681
|
-
if (exampleSection) {
|
|
682
|
-
for (const section of exampleSection) {
|
|
683
|
-
const sectionExamples = this.extractCodeBlocksFromSection(section);
|
|
684
|
-
examples.push(...sectionExamples.map(p => ({
|
|
685
|
-
description: p.description || 'Example implementation',
|
|
686
|
-
code: p.code,
|
|
687
|
-
language: p.language
|
|
688
|
-
})));
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
return examples;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* Extract cost impact information
|
|
697
|
-
*/
|
|
698
|
-
extractCostImpact(content) {
|
|
699
|
-
const costImpact = {};
|
|
700
|
-
|
|
701
|
-
// Look for cost comparison tables
|
|
702
|
-
const costTable = content.match(/\|[^|]*Cost[^|]*\|[\s\S]*?\n\n/i);
|
|
703
|
-
if (costTable) {
|
|
704
|
-
costImpact.hasTable = true;
|
|
705
|
-
costImpact.details = costTable[0].trim();
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
// Look for specific cost mentions
|
|
709
|
-
const costMentions = content.match(/\$\d+[^.\n]*/g);
|
|
710
|
-
if (costMentions) {
|
|
711
|
-
costImpact.mentions = costMentions.map(m => m.trim());
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// Look for "Cost Impact" sections
|
|
715
|
-
const costSection = content.match(/##?\s*Cost Impact[^#]*/i);
|
|
716
|
-
if (costSection) {
|
|
717
|
-
costImpact.section = costSection[0].trim();
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
return Object.keys(costImpact).length > 0 ? costImpact : null;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* Determine applicable file patterns based on category and content
|
|
725
|
-
*/
|
|
726
|
-
extractApplicableFiles(content, category) {
|
|
727
|
-
const patterns = [];
|
|
728
|
-
|
|
729
|
-
// Category-based patterns
|
|
730
|
-
const categoryPatterns = {
|
|
731
|
-
'serverless-saas-aws': ['**/*.js', '**/template.yaml', '**/handlers/**/*.js'],
|
|
732
|
-
'frontend-development': ['**/*.tsx', '**/*.jsx', '**/*.ts', 'src/frontend/**/*'],
|
|
733
|
-
'database': ['**/*.sql', '**/dbOperations.js', '**/migrations/**/*'],
|
|
734
|
-
'backend': ['src/backend/**/*.js', 'src/handlers/**/*.js'],
|
|
735
|
-
'multi-agent-orchestration': ['src/agents/**/*.js', 'src/core/**/*.js']
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
if (categoryPatterns[category]) {
|
|
739
|
-
patterns.push(...categoryPatterns[category]);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
// Content-based patterns
|
|
743
|
-
if (content.includes('Lambda') || content.includes('handler')) {
|
|
744
|
-
patterns.push('src/handlers/**/*.js');
|
|
745
|
-
}
|
|
746
|
-
if (content.includes('React') || content.includes('component')) {
|
|
747
|
-
patterns.push('src/frontend/**/*.tsx', 'src/frontend/**/*.jsx');
|
|
748
|
-
}
|
|
749
|
-
if (content.includes('PostgreSQL') || content.includes('database')) {
|
|
750
|
-
patterns.push('**/*dbOperations.js', '**/*.sql');
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
return [...new Set(patterns)]; // Remove duplicates
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Extract code blocks from a section
|
|
758
|
-
*/
|
|
759
|
-
extractCodeBlocksFromSection(section) {
|
|
760
|
-
const blocks = [];
|
|
761
|
-
const codeBlocks = section.match(/```(\w+)?\n([\s\S]+?)```/g);
|
|
762
|
-
|
|
763
|
-
if (codeBlocks) {
|
|
764
|
-
for (const block of codeBlocks) {
|
|
765
|
-
const match = block.match(/```(\w+)?\n([\s\S]+?)```/);
|
|
766
|
-
if (match) {
|
|
767
|
-
// Look for description before code block
|
|
768
|
-
const beforeBlock = section.substring(0, section.indexOf(block));
|
|
769
|
-
const descMatch = beforeBlock.match(/([^\n]+)\n*$/);
|
|
770
|
-
|
|
771
|
-
blocks.push({
|
|
772
|
-
description: descMatch ? descMatch[1].trim() : null,
|
|
773
|
-
code: match[2].trim(),
|
|
774
|
-
language: match[1] || 'javascript'
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
return blocks;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
/**
|
|
784
|
-
* Infer programming language from code content
|
|
785
|
-
*/
|
|
786
|
-
inferLanguageFromCode(code) {
|
|
787
|
-
if (typeof code !== 'string') return 'text';
|
|
788
|
-
|
|
789
|
-
// YAML/SAM template patterns
|
|
790
|
-
if (code.includes('{{resolve:ssm:') || code.match(/^\s*(Environment|Resources|Properties):/m)) {
|
|
791
|
-
return 'yaml';
|
|
792
|
-
}
|
|
793
|
-
// JavaScript patterns
|
|
794
|
-
if (code.includes('async ') || code.includes('await ') || code.includes('const ') ||
|
|
795
|
-
code.includes('function ') || code.includes('=>') || code.includes('require(')) {
|
|
796
|
-
return 'javascript';
|
|
797
|
-
}
|
|
798
|
-
// SQL patterns
|
|
799
|
-
if (code.match(/\b(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER)\b/i)) {
|
|
800
|
-
return 'sql';
|
|
801
|
-
}
|
|
802
|
-
// Python patterns
|
|
803
|
-
if (code.includes('def ') || code.includes('import ') || code.match(/^\s*class\s+\w+:/m)) {
|
|
804
|
-
return 'python';
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
return 'text';
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
/**
|
|
811
|
-
* Extract element name from a rule statement
|
|
812
|
-
* Goal: Create a descriptive, unique element name from the rule
|
|
813
|
-
*/
|
|
814
|
-
extractElement(statement) {
|
|
815
|
-
// Remove action prefix (ALWAYS, NEVER, USE, etc.)
|
|
816
|
-
let cleaned = statement.replace(/^(ALWAYS|NEVER|USE|PREFER|AVOID|DO NOT|DON'T)[:\s]+/i, '').trim();
|
|
817
|
-
|
|
818
|
-
// Extract key technical terms for better element names
|
|
819
|
-
// Pattern: "verb + object" -> extract the object/concept being acted upon
|
|
820
|
-
// Allow method calls like "client.end()" by not stopping at dots within identifiers
|
|
821
|
-
const verbObjectMatch = cleaned.match(/^(?:Use|Implement|Apply|Follow|Add|Create|Set|Cache|Validate|Configure|Enable|Disable|Fetch|Call)\s+(.+?)(?:\s+-\s+|,|$)/i);
|
|
822
|
-
if (verbObjectMatch) {
|
|
823
|
-
let object = verbObjectMatch[1].trim();
|
|
824
|
-
// Clean up trailing punctuation but keep method calls
|
|
825
|
-
object = object.replace(/\s*[-–].*$/, ''); // Remove explanatory text after dash
|
|
826
|
-
return object.substring(0, 50);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// For statements starting with a noun phrase, extract more context
|
|
830
|
-
const nounPhraseMatch = cleaned.match(/^([A-Za-z][a-z]+(?:[\s.][a-z()]+){0,5})/i);
|
|
831
|
-
if (nounPhraseMatch) {
|
|
832
|
-
let phrase = nounPhraseMatch[1].trim();
|
|
833
|
-
phrase = phrase.replace(/\s*[-–].*$/, ''); // Remove explanatory text
|
|
834
|
-
return phrase.substring(0, 50);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
// Fallback: first meaningful words
|
|
838
|
-
const words = cleaned.split(/\s+/).slice(0, 4);
|
|
839
|
-
return words.join(' ').substring(0, 50);
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
/**
|
|
843
|
-
* Extract first paragraph from content
|
|
844
|
-
*/
|
|
845
|
-
extractFirstParagraph(content) {
|
|
846
|
-
const paragraphs = content.split(/\n\n+/);
|
|
847
|
-
for (const para of paragraphs) {
|
|
848
|
-
const cleaned = para.replace(/^#+\s+/, '').trim();
|
|
849
|
-
if (cleaned.length > 20 && !cleaned.startsWith('**')) {
|
|
850
|
-
return cleaned;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
return 'Standard pattern';
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Generate unique pattern ID
|
|
858
|
-
* @param {string} category - Category name
|
|
859
|
-
* @param {string} filename - Source filename
|
|
860
|
-
* @param {string} element - Element description
|
|
861
|
-
* @param {number} index - Rule index for uniqueness
|
|
862
|
-
*/
|
|
863
|
-
generatePatternId(category, filename, element, index = 0) {
|
|
864
|
-
const categorySlug = category.replace(/[^a-z0-9]/gi, '_');
|
|
865
|
-
const fileSlug = filename.replace(/[^a-z0-9]/gi, '_');
|
|
866
|
-
const elementSlug = element.replace(/[^a-z0-9]/gi, '_').toLowerCase().substring(0, 40);
|
|
867
|
-
|
|
868
|
-
// Include index for uniqueness when elements might collide
|
|
869
|
-
const suffix = index > 0 ? `_${index}` : '';
|
|
870
|
-
|
|
871
|
-
return `${categorySlug}_${fileSlug}_${elementSlug}${suffix}`.toLowerCase();
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
/**
|
|
875
|
-
* Store patterns in database
|
|
876
|
-
*/
|
|
877
|
-
async seedPatternDatabase(patterns) {
|
|
878
|
-
if (patterns.length === 0) {
|
|
879
|
-
console.log('[StandardsIngestion] No patterns to ingest');
|
|
880
|
-
return 0;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
console.log(`[StandardsIngestion] Storing ${patterns.length} patterns in database...`);
|
|
884
|
-
|
|
885
|
-
let successCount = 0;
|
|
886
|
-
|
|
887
|
-
for (const pattern of patterns) {
|
|
888
|
-
try {
|
|
889
|
-
await executeQuery(`
|
|
890
|
-
INSERT INTO rapport.standards_patterns (
|
|
891
|
-
pattern_id,
|
|
892
|
-
file_name,
|
|
893
|
-
element,
|
|
894
|
-
rule,
|
|
895
|
-
priority,
|
|
896
|
-
correlation,
|
|
897
|
-
source,
|
|
898
|
-
maturity,
|
|
899
|
-
scope,
|
|
900
|
-
category,
|
|
901
|
-
applicable_files,
|
|
902
|
-
anti_patterns,
|
|
903
|
-
examples,
|
|
904
|
-
cost_impact,
|
|
905
|
-
keywords,
|
|
906
|
-
created_at,
|
|
907
|
-
last_updated
|
|
908
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, NOW(), NOW())
|
|
909
|
-
ON CONFLICT (pattern_id)
|
|
910
|
-
DO UPDATE SET
|
|
911
|
-
file_name = EXCLUDED.file_name,
|
|
912
|
-
element = EXCLUDED.element,
|
|
913
|
-
rule = EXCLUDED.rule,
|
|
914
|
-
priority = EXCLUDED.priority,
|
|
915
|
-
correlation = EXCLUDED.correlation,
|
|
916
|
-
maturity = EXCLUDED.maturity,
|
|
917
|
-
scope = EXCLUDED.scope,
|
|
918
|
-
category = EXCLUDED.category,
|
|
919
|
-
applicable_files = EXCLUDED.applicable_files,
|
|
920
|
-
anti_patterns = EXCLUDED.anti_patterns,
|
|
921
|
-
examples = EXCLUDED.examples,
|
|
922
|
-
cost_impact = EXCLUDED.cost_impact,
|
|
923
|
-
keywords = EXCLUDED.keywords,
|
|
924
|
-
last_updated = NOW(),
|
|
925
|
-
last_seen_at = NOW(),
|
|
926
|
-
occurrence_count = COALESCE(rapport.standards_patterns.occurrence_count, 0) + 1
|
|
927
|
-
`, [
|
|
928
|
-
pattern.pattern_id,
|
|
929
|
-
pattern.file_name,
|
|
930
|
-
pattern.element,
|
|
931
|
-
pattern.rule,
|
|
932
|
-
pattern.priority,
|
|
933
|
-
pattern.correlation,
|
|
934
|
-
pattern.source,
|
|
935
|
-
pattern.maturity,
|
|
936
|
-
pattern.scope,
|
|
937
|
-
pattern.category,
|
|
938
|
-
pattern.applicable_files,
|
|
939
|
-
JSON.stringify(pattern.anti_patterns),
|
|
940
|
-
JSON.stringify(pattern.examples),
|
|
941
|
-
pattern.cost_impact ? JSON.stringify(pattern.cost_impact) : null,
|
|
942
|
-
pattern.tags || []
|
|
943
|
-
]);
|
|
944
|
-
|
|
945
|
-
successCount++;
|
|
946
|
-
|
|
947
|
-
if (this.verbose && successCount % 10 === 0) {
|
|
948
|
-
console.log(` Stored ${successCount}/${patterns.length} patterns...`);
|
|
949
|
-
}
|
|
950
|
-
} catch (error) {
|
|
951
|
-
console.error(` ✗ Failed to store pattern ${pattern.pattern_id}:`, error.message);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
console.log(`[StandardsIngestion] ✓ Successfully stored ${successCount} patterns`);
|
|
956
|
-
return successCount;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
/**
|
|
960
|
-
* Check if standards need re-ingestion (for performance)
|
|
961
|
-
*/
|
|
962
|
-
async needsIngestion() {
|
|
963
|
-
try {
|
|
964
|
-
const result = await executeQuery(`
|
|
965
|
-
SELECT COUNT(*) as count, MAX(last_updated) as last_updated
|
|
966
|
-
FROM rapport.standards_patterns
|
|
967
|
-
WHERE source = 'equilateral-standards'
|
|
968
|
-
`);
|
|
969
|
-
|
|
970
|
-
const row = result.rows[0];
|
|
971
|
-
|
|
972
|
-
// Re-ingest if no patterns exist or haven't been updated in 7 days
|
|
973
|
-
if (row.count === '0') {
|
|
974
|
-
return { needed: true, reason: 'No standards ingested yet' };
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
const lastUpdated = new Date(row.last_updated);
|
|
978
|
-
const daysSince = (Date.now() - lastUpdated.getTime()) / (1000 * 60 * 60 * 24);
|
|
979
|
-
|
|
980
|
-
if (daysSince > 7) {
|
|
981
|
-
return { needed: true, reason: `Last updated ${daysSince.toFixed(0)} days ago` };
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
return { needed: false, count: row.count };
|
|
985
|
-
} catch (error) {
|
|
986
|
-
return { needed: true, reason: 'Database check failed' };
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
module.exports = { StandardsIngestion };
|