@equilateral_ai/mindmeld 3.5.2 → 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 (140) hide show
  1. package/hooks/session-end.js +25 -0
  2. package/hooks/session-start.js +363 -83
  3. package/hooks/session-watcher.js +585 -0
  4. package/package.json +19 -13
  5. package/scripts/init-project.js +9 -23
  6. package/src/client/dbShim.js +16 -0
  7. package/src/core/AuthManager.js +3 -2
  8. package/src/handlers/helpers/dbOperations.js +9 -46
  9. package/src/index.js +2 -217
  10. package/src/utils/piiMask.js +16 -0
  11. package/scripts/harvest.js +0 -601
  12. package/scripts/inject.js +0 -409
  13. package/scripts/mcp-bridge.js +0 -220
  14. package/scripts/repo-analyzer.js +0 -870
  15. package/src/collaboration/CollaborationPrompt.js +0 -460
  16. package/src/core/AlertEngine.js +0 -813
  17. package/src/core/AlertNotifier.js +0 -363
  18. package/src/core/CorrelationAnalyzer.js +0 -931
  19. package/src/core/CrossReferenceEngine.js +0 -624
  20. package/src/core/CurationEngine.js +0 -688
  21. package/src/core/DeprecationScheduler.js +0 -183
  22. package/src/core/LoadBearingDetector.js +0 -242
  23. package/src/core/NotificationService.js +0 -1032
  24. package/src/core/RapportOrchestrator.js +0 -632
  25. package/src/core/RelevanceDetector.js +0 -694
  26. package/src/core/StandardLifecycle.js +0 -244
  27. package/src/core/StandardsIngestion.js +0 -991
  28. package/src/core/TeamLoadBearingDetector.js +0 -431
  29. package/src/core/parsers/adrParser.js +0 -479
  30. package/src/core/parsers/cursorRulesParser.js +0 -564
  31. package/src/core/parsers/eslintParser.js +0 -439
  32. package/src/database/dbOperations.js +0 -105
  33. package/src/handlers/activity/activityGetMe.js +0 -98
  34. package/src/handlers/activity/activityGetTeam.js +0 -175
  35. package/src/handlers/admin/adminSetup.js +0 -216
  36. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  37. package/src/handlers/alerts/alertsGet.js +0 -250
  38. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  39. package/src/handlers/analytics/coachingGet.js +0 -361
  40. package/src/handlers/analytics/convergenceGet.js +0 -236
  41. package/src/handlers/analytics/developerScoreGet.js +0 -137
  42. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  43. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  44. package/src/handlers/collaborators/collaboratorList.js +0 -82
  45. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  46. package/src/handlers/collaborators/inviteAccept.js +0 -122
  47. package/src/handlers/company/companyUsersDelete.js +0 -141
  48. package/src/handlers/company/companyUsersGet.js +0 -90
  49. package/src/handlers/company/companyUsersPost.js +0 -267
  50. package/src/handlers/company/companyUsersPut.js +0 -76
  51. package/src/handlers/context/contextGet.js +0 -57
  52. package/src/handlers/context/invariantsGet.js +0 -74
  53. package/src/handlers/context/loopsGet.js +0 -82
  54. package/src/handlers/context/notesCreate.js +0 -74
  55. package/src/handlers/context/purposeGet.js +0 -78
  56. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  57. package/src/handlers/correlations/correlationsGet.js +0 -93
  58. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  59. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  60. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  61. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  62. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  63. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  64. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  65. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  66. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  67. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  68. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  69. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  70. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  71. package/src/handlers/github/githubConnectionStatus.js +0 -49
  72. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  73. package/src/handlers/github/githubOAuthCallback.js +0 -178
  74. package/src/handlers/github/githubOAuthStart.js +0 -59
  75. package/src/handlers/github/githubPatternsReview.js +0 -76
  76. package/src/handlers/github/githubReposList.js +0 -105
  77. package/src/handlers/health/healthGet.js +0 -55
  78. package/src/handlers/helpers/auditLogger.js +0 -201
  79. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  80. package/src/handlers/helpers/decisionFrames.js +0 -29
  81. package/src/handlers/helpers/errorHandler.js +0 -49
  82. package/src/handlers/helpers/index.js +0 -138
  83. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  84. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  85. package/src/handlers/helpers/predictiveCache.js +0 -51
  86. package/src/handlers/helpers/projectAccess.js +0 -88
  87. package/src/handlers/helpers/responseUtil.js +0 -55
  88. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  89. package/src/handlers/mcp/mcpHandler.js +0 -569
  90. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  91. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  92. package/src/handlers/notifications/getPreferences.js +0 -84
  93. package/src/handlers/notifications/sendNotification.js +0 -170
  94. package/src/handlers/notifications/updatePreferences.js +0 -316
  95. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  96. package/src/handlers/patterns/patternUsagePost.js +0 -182
  97. package/src/handlers/patterns/patternViolationPost.js +0 -185
  98. package/src/handlers/projects/projectCreate.js +0 -248
  99. package/src/handlers/projects/projectDelete.js +0 -82
  100. package/src/handlers/projects/projectGet.js +0 -95
  101. package/src/handlers/projects/projectUpdate.js +0 -117
  102. package/src/handlers/reports/aiLeverage.js +0 -210
  103. package/src/handlers/reports/engineeringInvestment.js +0 -132
  104. package/src/handlers/reports/riskForecast.js +0 -206
  105. package/src/handlers/reports/standardsRoi.js +0 -254
  106. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  107. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  108. package/src/handlers/scheduled/generateAlerts.js +0 -135
  109. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  110. package/src/handlers/scheduled/refreshActivity.js +0 -21
  111. package/src/handlers/scheduled/scanCompliance.js +0 -334
  112. package/src/handlers/sessions/sessionEndPost.js +0 -180
  113. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  114. package/src/handlers/standards/catalogGet.js +0 -185
  115. package/src/handlers/standards/catalogSync.js +0 -120
  116. package/src/handlers/standards/discoveriesGet.js +0 -89
  117. package/src/handlers/standards/projectStandardsGet.js +0 -129
  118. package/src/handlers/standards/projectStandardsPut.js +0 -151
  119. package/src/handlers/standards/standardsAuditGet.js +0 -65
  120. package/src/handlers/standards/standardsParseUpload.js +0 -149
  121. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  122. package/src/handlers/standards/standardsTransition.js +0 -161
  123. package/src/handlers/stripe/addonManagePost.js +0 -240
  124. package/src/handlers/stripe/billingPortalPost.js +0 -93
  125. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  126. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  127. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  128. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  129. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  130. package/src/handlers/stripe/webhookPost.js +0 -482
  131. package/src/handlers/user/apiTokenCreate.js +0 -71
  132. package/src/handlers/user/apiTokenList.js +0 -64
  133. package/src/handlers/user/userSplashAck.js +0 -91
  134. package/src/handlers/user/userSplashGet.js +0 -211
  135. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  136. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  137. package/src/handlers/users/userEntitlementsGet.js +0 -89
  138. package/src/handlers/users/userGet.js +0 -118
  139. package/src/handlers/users/userProfilePut.js +0 -77
  140. package/src/handlers/webhooks/githubWebhook.js +0 -215
@@ -1,479 +0,0 @@
1
- /**
2
- * ADR Parser - Parse Architecture Decision Records into YAML standards format
3
- *
4
- * Parses ADR markdown files with standard sections:
5
- * Title, Status, Context, Decision, Consequences
6
- *
7
- * Extracts:
8
- * - Rules from Decision section with action types (ALWAYS/NEVER/USE/PREFER/AVOID)
9
- * - Anti-patterns from Consequences negative items
10
- * - Context for applicability
11
- *
12
- * Returns YAML-compatible object matching equilateral-standards schema:
13
- * { id, category, priority, rules, anti_patterns, context }
14
- *
15
- * @module parsers/adrParser
16
- */
17
-
18
- /**
19
- * Action keywords that indicate rule types in decision text
20
- */
21
- const ACTION_PATTERNS = [
22
- { regex: /\b(?:must|shall|always|required)\b/i, action: 'ALWAYS' },
23
- { regex: /\b(?:must not|shall not|never|prohibited|forbidden)\b/i, action: 'NEVER' },
24
- { regex: /\b(?:will use|adopt|choose|selected|use)\b/i, action: 'USE' },
25
- { regex: /\b(?:prefer|should|recommended|favor)\b/i, action: 'PREFER' },
26
- { regex: /\b(?:avoid|discourage|should not|minimize)\b/i, action: 'AVOID' }
27
- ];
28
-
29
- /**
30
- * Parse ADR markdown content into YAML-compatible standards object
31
- *
32
- * @param {string} content - Raw ADR markdown content
33
- * @param {Object} options - Parser options
34
- * @param {string} options.filename - Source filename for ID generation
35
- * @param {string} options.category - Standards category (e.g., 'backend', 'database')
36
- * @returns {Object} YAML-compatible standards object
37
- */
38
- function parseAdr(content, options = {}) {
39
- if (!content || typeof content !== 'string') {
40
- throw new Error('ADR content is required and must be a string');
41
- }
42
-
43
- const filename = options.filename || 'unknown-adr';
44
- const category = options.category || inferCategory(content);
45
-
46
- const title = extractTitle(content);
47
- const status = extractStatus(content);
48
- const context = extractSection(content, 'Context');
49
- const decision = extractSection(content, 'Decision');
50
- const consequences = extractSection(content, 'Consequences');
51
-
52
- const id = generateId(filename, title);
53
- const rules = extractRules(decision);
54
- const antiPatterns = extractAntiPatterns(consequences);
55
- const priority = determinePriority(status, rules);
56
-
57
- return {
58
- id,
59
- category,
60
- priority,
61
- rules,
62
- anti_patterns: antiPatterns,
63
- context: {
64
- title,
65
- status,
66
- description: context || '',
67
- source_format: 'adr',
68
- source_file: filename
69
- },
70
- tags: extractTags(content, category),
71
- updated: new Date().toISOString().split('T')[0]
72
- };
73
- }
74
-
75
- /**
76
- * Extract the ADR title from the first heading
77
- *
78
- * @param {string} content - ADR markdown content
79
- * @returns {string} Extracted title
80
- */
81
- function extractTitle(content) {
82
- // ADR titles often follow pattern: "# ADR-NNN: Title" or "# Title"
83
- const match = content.match(/^#\s+(?:ADR[-\s]?\d+[:\s-]+)?(.+)$/m);
84
- return match ? match[1].trim() : 'Untitled ADR';
85
- }
86
-
87
- /**
88
- * Extract the ADR status
89
- *
90
- * @param {string} content - ADR markdown content
91
- * @returns {string} ADR status
92
- */
93
- function extractStatus(content) {
94
- const statusSection = extractSection(content, 'Status');
95
- if (!statusSection) return 'proposed';
96
-
97
- const statusMatch = statusSection.match(/\b(accepted|proposed|deprecated|superseded|rejected)\b/i);
98
- return statusMatch ? statusMatch[1].toLowerCase() : 'proposed';
99
- }
100
-
101
- /**
102
- * Extract a named section from ADR markdown
103
- *
104
- * @param {string} content - ADR markdown content
105
- * @param {string} sectionName - Name of the section to extract
106
- * @returns {string|null} Section content or null
107
- */
108
- function extractSection(content, sectionName) {
109
- // Split content by section headers and find the matching one
110
- const sectionRegex = /^(##\s+.+)$/gm;
111
- const headers = [];
112
- let match;
113
-
114
- while ((match = sectionRegex.exec(content)) !== null) {
115
- headers.push({ text: match[1], index: match.index });
116
- }
117
-
118
- for (let i = 0; i < headers.length; i++) {
119
- const headerText = headers[i].text.replace(/^##\s+/, '').trim();
120
- if (headerText.toLowerCase() === sectionName.toLowerCase()) {
121
- const startIndex = headers[i].index + headers[i].text.length;
122
- const endIndex = (i + 1 < headers.length) ? headers[i + 1].index : content.length;
123
- const sectionContent = content.substring(startIndex, endIndex).trim();
124
- return sectionContent || null;
125
- }
126
- }
127
-
128
- return null;
129
- }
130
-
131
- /**
132
- * Extract rules from the Decision section
133
- * Maps decision statements to YAML rules with action types
134
- *
135
- * @param {string} decisionText - Content of the Decision section
136
- * @returns {Array<Object>} Array of rule objects with action and rule text
137
- */
138
- function extractRules(decisionText) {
139
- if (!decisionText) return [];
140
-
141
- const rules = [];
142
- const lines = decisionText.split('\n');
143
-
144
- // Process list items and standalone sentences
145
- const statements = [];
146
-
147
- for (const line of lines) {
148
- const trimmed = line.trim();
149
-
150
- // Capture bullet/numbered list items
151
- const listMatch = trimmed.match(/^[-*\d]+\.?\s+(.+)$/);
152
- if (listMatch) {
153
- statements.push(listMatch[1].trim());
154
- continue;
155
- }
156
-
157
- // Capture sentences that contain action keywords
158
- if (trimmed.length > 15 && containsActionKeyword(trimmed)) {
159
- statements.push(trimmed);
160
- }
161
- }
162
-
163
- // Also split paragraph text into sentences for action keyword detection
164
- const paragraphText = lines
165
- .filter(l => !l.trim().match(/^[-*\d]+\./))
166
- .join(' ')
167
- .trim();
168
-
169
- const sentences = paragraphText.split(/(?<=[.!])\s+/);
170
- for (const sentence of sentences) {
171
- const cleaned = sentence.trim();
172
- if (cleaned.length > 15 && containsActionKeyword(cleaned)) {
173
- // Avoid duplicates with list items
174
- const isDuplicate = statements.some(s =>
175
- s.includes(cleaned) || cleaned.includes(s)
176
- );
177
- if (!isDuplicate) {
178
- statements.push(cleaned);
179
- }
180
- }
181
- }
182
-
183
- for (const statement of statements) {
184
- const action = classifyAction(statement);
185
- const ruleText = cleanRuleText(statement, action);
186
-
187
- if (ruleText.length > 5) {
188
- rules.push({
189
- action,
190
- rule: ruleText
191
- });
192
- }
193
- }
194
-
195
- // If no specific rules extracted, create a general USE rule from the decision
196
- if (rules.length === 0 && decisionText.length > 20) {
197
- const summary = decisionText.split('\n')[0].trim();
198
- if (summary.length > 10) {
199
- rules.push({
200
- action: 'USE',
201
- rule: summary.replace(/^We\s+(will|have decided to)\s+/i, '').trim()
202
- });
203
- }
204
- }
205
-
206
- return rules;
207
- }
208
-
209
- /**
210
- * Extract anti-patterns from the Consequences section
211
- * Negative consequences indicate what to avoid
212
- *
213
- * @param {string} consequencesText - Content of the Consequences section
214
- * @returns {Array<string>} Array of anti-pattern descriptions
215
- */
216
- function extractAntiPatterns(consequencesText) {
217
- if (!consequencesText) return [];
218
-
219
- const antiPatterns = [];
220
- const lines = consequencesText.split('\n');
221
-
222
- let inNegativeSection = false;
223
-
224
- for (const line of lines) {
225
- const trimmed = line.trim();
226
-
227
- // Detect negative subsection headers
228
- if (trimmed.match(/^###?\s*(negative|bad|risks?|downsides?|drawbacks?|disadvantages?)/i)) {
229
- inNegativeSection = true;
230
- continue;
231
- }
232
-
233
- // Detect positive subsection headers (exit negative section)
234
- if (trimmed.match(/^###?\s*(positive|good|benefits?|advantages?|upsides?)/i)) {
235
- inNegativeSection = false;
236
- continue;
237
- }
238
-
239
- // Detect neutral subsection headers
240
- if (trimmed.match(/^###?\s*(neutral)/i)) {
241
- inNegativeSection = false;
242
- continue;
243
- }
244
-
245
- // Capture list items in negative sections
246
- if (inNegativeSection) {
247
- const listMatch = trimmed.match(/^[-*]\s+(.+)$/);
248
- if (listMatch && listMatch[1].length > 10) {
249
- antiPatterns.push(listMatch[1].trim());
250
- }
251
- continue;
252
- }
253
-
254
- // Capture items with negative markers anywhere in consequences
255
- const negativeMatch = trimmed.match(/^[-*]\s*(?:(?:Bad|Negative|Risk|Downside)[:\s]+)?(.+)$/i);
256
- if (negativeMatch) {
257
- const item = negativeMatch[1].trim();
258
- if (isNegativeConsequence(item)) {
259
- antiPatterns.push(item);
260
- }
261
- }
262
- }
263
-
264
- return antiPatterns;
265
- }
266
-
267
- /**
268
- * Check if a consequence line indicates a negative outcome
269
- *
270
- * @param {string} text - Consequence text
271
- * @returns {boolean} True if the text describes a negative consequence
272
- */
273
- function isNegativeConsequence(text) {
274
- const negativeIndicators = [
275
- /\brisk\b/i,
276
- /\bcomplexity\b/i,
277
- /\bdifficult\b/i,
278
- /\boverhead\b/i,
279
- /\bcost\b/i,
280
- /\bslower\b/i,
281
- /\blimitation\b/i,
282
- /\bdrawback\b/i,
283
- /\bdownside\b/i,
284
- /\bchalleng/i,
285
- /\bbreaking\b/i,
286
- /\bmigration\b/i,
287
- /\blearning curve\b/i,
288
- /\bcoupling\b/i,
289
- /\block-in\b/i,
290
- /\btechnical debt\b/i,
291
- /\bneed(?:s)?\s+to\b/i,
292
- /\brequire(?:s)?\s+(?:additional|extra|more)\b/i
293
- ];
294
-
295
- return negativeIndicators.some(pattern => pattern.test(text));
296
- }
297
-
298
- /**
299
- * Check if text contains an action keyword
300
- *
301
- * @param {string} text - Text to check
302
- * @returns {boolean} True if text contains an action keyword
303
- */
304
- function containsActionKeyword(text) {
305
- return ACTION_PATTERNS.some(p => p.regex.test(text));
306
- }
307
-
308
- /**
309
- * Classify the action type for a decision statement
310
- *
311
- * @param {string} statement - Decision statement
312
- * @returns {string} Action type: ALWAYS, NEVER, USE, PREFER, or AVOID
313
- */
314
- function classifyAction(statement) {
315
- // Check NEVER before ALWAYS since "must not" contains "must"
316
- for (const { regex, action } of ACTION_PATTERNS) {
317
- if (action === 'NEVER' && regex.test(statement)) return 'NEVER';
318
- }
319
- for (const { regex, action } of ACTION_PATTERNS) {
320
- if (action === 'AVOID' && regex.test(statement)) return 'AVOID';
321
- }
322
- for (const { regex, action } of ACTION_PATTERNS) {
323
- if (action === 'ALWAYS' && regex.test(statement)) return 'ALWAYS';
324
- }
325
- for (const { regex, action } of ACTION_PATTERNS) {
326
- if (action === 'PREFER' && regex.test(statement)) return 'PREFER';
327
- }
328
- for (const { regex, action } of ACTION_PATTERNS) {
329
- if (action === 'USE' && regex.test(statement)) return 'USE';
330
- }
331
-
332
- return 'USE';
333
- }
334
-
335
- /**
336
- * Clean rule text by removing leading action verbs for cleaner output
337
- *
338
- * @param {string} text - Raw rule text
339
- * @param {string} action - Classified action type
340
- * @returns {string} Cleaned rule text
341
- */
342
- function cleanRuleText(text, action) {
343
- let cleaned = text
344
- .replace(/^We\s+(will|have decided to|decided to|should)\s+/i, '')
345
- .replace(/^(Must|Shall|Always|Never|Should|Should not|Must not)\s+/i, '')
346
- .replace(/\s*\.$/, '')
347
- .trim();
348
-
349
- // Capitalize first letter
350
- if (cleaned.length > 0) {
351
- cleaned = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
352
- }
353
-
354
- return cleaned;
355
- }
356
-
357
- /**
358
- * Generate a standards-compatible ID from filename and title
359
- *
360
- * @param {string} filename - Source filename
361
- * @param {string} title - ADR title
362
- * @returns {string} Generated ID
363
- */
364
- function generateId(filename, title) {
365
- // Use filename slug if it looks like an ADR (e.g., "0001-use-react.md")
366
- const filenameSlug = filename
367
- .replace(/\.md$/i, '')
368
- .replace(/[^a-z0-9]+/gi, '-')
369
- .toLowerCase();
370
-
371
- if (filenameSlug.match(/^\d+-/)) {
372
- return `adr-${filenameSlug}`;
373
- }
374
-
375
- // Fall back to title-based ID
376
- const titleSlug = title
377
- .replace(/[^a-z0-9]+/gi, '-')
378
- .toLowerCase()
379
- .substring(0, 60);
380
-
381
- return `adr-${titleSlug}`;
382
- }
383
-
384
- /**
385
- * Infer category from ADR content
386
- *
387
- * @param {string} content - ADR markdown content
388
- * @returns {string} Inferred category
389
- */
390
- function inferCategory(content) {
391
- const lower = content.toLowerCase();
392
-
393
- const categoryKeywords = {
394
- 'serverless-saas-aws': ['lambda', 'api gateway', 'serverless', 'aws', 'sam template', 'cloudformation'],
395
- 'frontend-development': ['react', 'vue', 'angular', 'frontend', 'component', 'ui', 'css', 'tsx', 'jsx'],
396
- 'database': ['database', 'postgresql', 'sql', 'schema', 'migration', 'query', 'orm'],
397
- 'backend': ['api', 'service', 'handler', 'endpoint', 'rest', 'graphql'],
398
- 'multi-agent-orchestration': ['agent', 'orchestration', 'llm', 'ai', 'workflow'],
399
- 'compliance-security': ['security', 'authentication', 'authorization', 'encryption', 'compliance', 'audit'],
400
- 'cost-optimization': ['cost', 'performance', 'optimization', 'scaling', 'caching'],
401
- 'testing': ['test', 'testing', 'coverage', 'ci/cd', 'pipeline'],
402
- 'deployment': ['deploy', 'infrastructure', 'docker', 'kubernetes', 'ci/cd']
403
- };
404
-
405
- for (const [category, keywords] of Object.entries(categoryKeywords)) {
406
- const matchCount = keywords.filter(kw => lower.includes(kw)).length;
407
- if (matchCount >= 2) return category;
408
- }
409
-
410
- // Single keyword fallback
411
- for (const [category, keywords] of Object.entries(categoryKeywords)) {
412
- if (keywords.some(kw => lower.includes(kw))) return category;
413
- }
414
-
415
- return 'general';
416
- }
417
-
418
- /**
419
- * Determine priority based on ADR status and rule types
420
- * Priority values: 10 (high), 20 (medium), 30 (low)
421
- *
422
- * @param {string} status - ADR status
423
- * @param {Array} rules - Extracted rules
424
- * @returns {number} Priority value
425
- */
426
- function determinePriority(status, rules) {
427
- // Deprecated/superseded ADRs are low priority
428
- if (status === 'deprecated' || status === 'superseded') return 30;
429
-
430
- // If rules contain ALWAYS or NEVER, higher priority
431
- const hasEnforcedRules = rules.some(r =>
432
- r.action === 'ALWAYS' || r.action === 'NEVER'
433
- );
434
-
435
- if (hasEnforcedRules && status === 'accepted') return 10;
436
- if (status === 'accepted') return 20;
437
-
438
- return 30;
439
- }
440
-
441
- /**
442
- * Extract relevant tags from ADR content
443
- *
444
- * @param {string} content - ADR content
445
- * @param {string} category - Assigned category
446
- * @returns {Array<string>} Tags
447
- */
448
- function extractTags(content, category) {
449
- const tags = ['adr', category];
450
- const lower = content.toLowerCase();
451
-
452
- const tagKeywords = {
453
- 'architecture': /\barchitectur/i,
454
- 'breaking-change': /\bbreaking\s+change/i,
455
- 'migration': /\bmigration/i,
456
- 'security': /\bsecurity\b/i,
457
- 'performance': /\bperformance\b/i,
458
- 'scalability': /\bscalability\b/i,
459
- 'maintainability': /\bmaintainability\b/i,
460
- 'testing': /\btesting\b/i
461
- };
462
-
463
- for (const [tag, pattern] of Object.entries(tagKeywords)) {
464
- if (pattern.test(lower)) {
465
- tags.push(tag);
466
- }
467
- }
468
-
469
- return [...new Set(tags)];
470
- }
471
-
472
- module.exports = {
473
- parseAdr,
474
- extractTitle,
475
- extractStatus,
476
- extractSection,
477
- extractRules,
478
- extractAntiPatterns
479
- };