@equilateral_ai/mindmeld 3.5.3 → 4.0.1

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