@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.
Files changed (138) hide show
  1. package/hooks/session-start.js +312 -85
  2. package/package.json +21 -13
  3. package/scripts/init-project.js +9 -23
  4. package/scripts/repo-analyzer.js +118 -2
  5. package/src/client/dbShim.js +16 -0
  6. package/src/core/AuthManager.js +3 -2
  7. package/src/handlers/helpers/dbOperations.js +9 -46
  8. package/src/index.js +2 -217
  9. package/src/utils/piiMask.js +16 -0
  10. package/scripts/inject.js +0 -409
  11. package/scripts/mcp-bridge.js +0 -220
  12. package/scripts/standards.js +0 -285
  13. package/src/collaboration/CollaborationPrompt.js +0 -460
  14. package/src/core/AlertEngine.js +0 -813
  15. package/src/core/AlertNotifier.js +0 -363
  16. package/src/core/CorrelationAnalyzer.js +0 -931
  17. package/src/core/CrossReferenceEngine.js +0 -624
  18. package/src/core/CurationEngine.js +0 -688
  19. package/src/core/DeprecationScheduler.js +0 -183
  20. package/src/core/LoadBearingDetector.js +0 -242
  21. package/src/core/NotificationService.js +0 -1032
  22. package/src/core/RapportOrchestrator.js +0 -632
  23. package/src/core/RelevanceDetector.js +0 -694
  24. package/src/core/StandardLifecycle.js +0 -244
  25. package/src/core/StandardsIngestion.js +0 -991
  26. package/src/core/TeamLoadBearingDetector.js +0 -431
  27. package/src/core/parsers/adrParser.js +0 -479
  28. package/src/core/parsers/cursorRulesParser.js +0 -564
  29. package/src/core/parsers/eslintParser.js +0 -439
  30. package/src/database/dbOperations.js +0 -105
  31. package/src/handlers/activity/activityGetMe.js +0 -98
  32. package/src/handlers/activity/activityGetTeam.js +0 -175
  33. package/src/handlers/admin/adminSetup.js +0 -216
  34. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  35. package/src/handlers/alerts/alertsGet.js +0 -250
  36. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  37. package/src/handlers/analytics/coachingGet.js +0 -361
  38. package/src/handlers/analytics/convergenceGet.js +0 -236
  39. package/src/handlers/analytics/developerScoreGet.js +0 -137
  40. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  41. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  42. package/src/handlers/collaborators/collaboratorList.js +0 -82
  43. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  44. package/src/handlers/collaborators/inviteAccept.js +0 -122
  45. package/src/handlers/company/companyUsersDelete.js +0 -141
  46. package/src/handlers/company/companyUsersGet.js +0 -90
  47. package/src/handlers/company/companyUsersPost.js +0 -267
  48. package/src/handlers/company/companyUsersPut.js +0 -76
  49. package/src/handlers/context/contextGet.js +0 -57
  50. package/src/handlers/context/invariantsGet.js +0 -74
  51. package/src/handlers/context/loopsGet.js +0 -82
  52. package/src/handlers/context/notesCreate.js +0 -74
  53. package/src/handlers/context/purposeGet.js +0 -78
  54. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  55. package/src/handlers/correlations/correlationsGet.js +0 -93
  56. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  57. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  58. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  59. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  60. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  61. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  62. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  63. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  64. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  65. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  66. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  67. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  68. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  69. package/src/handlers/github/githubConnectionStatus.js +0 -49
  70. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  71. package/src/handlers/github/githubOAuthCallback.js +0 -178
  72. package/src/handlers/github/githubOAuthStart.js +0 -59
  73. package/src/handlers/github/githubPatternsReview.js +0 -76
  74. package/src/handlers/github/githubReposList.js +0 -105
  75. package/src/handlers/health/healthGet.js +0 -55
  76. package/src/handlers/helpers/auditLogger.js +0 -201
  77. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  78. package/src/handlers/helpers/decisionFrames.js +0 -29
  79. package/src/handlers/helpers/errorHandler.js +0 -49
  80. package/src/handlers/helpers/index.js +0 -138
  81. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  82. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  83. package/src/handlers/helpers/predictiveCache.js +0 -51
  84. package/src/handlers/helpers/projectAccess.js +0 -88
  85. package/src/handlers/helpers/responseUtil.js +0 -55
  86. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  87. package/src/handlers/mcp/mcpHandler.js +0 -569
  88. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  89. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  90. package/src/handlers/notifications/getPreferences.js +0 -84
  91. package/src/handlers/notifications/sendNotification.js +0 -170
  92. package/src/handlers/notifications/updatePreferences.js +0 -316
  93. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  94. package/src/handlers/patterns/patternUsagePost.js +0 -182
  95. package/src/handlers/patterns/patternViolationPost.js +0 -185
  96. package/src/handlers/projects/projectCreate.js +0 -248
  97. package/src/handlers/projects/projectDelete.js +0 -82
  98. package/src/handlers/projects/projectGet.js +0 -95
  99. package/src/handlers/projects/projectUpdate.js +0 -117
  100. package/src/handlers/reports/aiLeverage.js +0 -210
  101. package/src/handlers/reports/engineeringInvestment.js +0 -132
  102. package/src/handlers/reports/riskForecast.js +0 -206
  103. package/src/handlers/reports/standardsRoi.js +0 -254
  104. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  105. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  106. package/src/handlers/scheduled/generateAlerts.js +0 -135
  107. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  108. package/src/handlers/scheduled/refreshActivity.js +0 -21
  109. package/src/handlers/scheduled/scanCompliance.js +0 -334
  110. package/src/handlers/sessions/sessionEndPost.js +0 -180
  111. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  112. package/src/handlers/standards/catalogGet.js +0 -185
  113. package/src/handlers/standards/catalogSync.js +0 -120
  114. package/src/handlers/standards/discoveriesGet.js +0 -89
  115. package/src/handlers/standards/projectStandardsGet.js +0 -129
  116. package/src/handlers/standards/projectStandardsPut.js +0 -151
  117. package/src/handlers/standards/standardsAuditGet.js +0 -65
  118. package/src/handlers/standards/standardsParseUpload.js +0 -149
  119. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  120. package/src/handlers/standards/standardsTransition.js +0 -161
  121. package/src/handlers/stripe/addonManagePost.js +0 -240
  122. package/src/handlers/stripe/billingPortalPost.js +0 -93
  123. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  124. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  125. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  126. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  127. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  128. package/src/handlers/stripe/webhookPost.js +0 -482
  129. package/src/handlers/user/apiTokenCreate.js +0 -71
  130. package/src/handlers/user/apiTokenList.js +0 -64
  131. package/src/handlers/user/userSplashAck.js +0 -91
  132. package/src/handlers/user/userSplashGet.js +0 -211
  133. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  134. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  135. package/src/handlers/users/userEntitlementsGet.js +0 -89
  136. package/src/handlers/users/userGet.js +0 -118
  137. package/src/handlers/users/userProfilePut.js +0 -77
  138. 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 };