@equilateral_ai/mindmeld 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +300 -0
  2. package/hooks/README.md +494 -0
  3. package/hooks/pre-compact.js +392 -0
  4. package/hooks/session-start.js +264 -0
  5. package/package.json +90 -0
  6. package/scripts/harvest.js +561 -0
  7. package/scripts/init-project.js +437 -0
  8. package/scripts/inject.js +388 -0
  9. package/src/collaboration/CollaborationPrompt.js +460 -0
  10. package/src/core/AlertEngine.js +813 -0
  11. package/src/core/AlertNotifier.js +363 -0
  12. package/src/core/CorrelationAnalyzer.js +774 -0
  13. package/src/core/CurationEngine.js +688 -0
  14. package/src/core/LLMPatternDetector.js +508 -0
  15. package/src/core/LoadBearingDetector.js +242 -0
  16. package/src/core/NotificationService.js +1032 -0
  17. package/src/core/PatternValidator.js +355 -0
  18. package/src/core/README.md +160 -0
  19. package/src/core/RapportOrchestrator.js +446 -0
  20. package/src/core/RelevanceDetector.js +577 -0
  21. package/src/core/StandardsIngestion.js +575 -0
  22. package/src/core/TeamLoadBearingDetector.js +431 -0
  23. package/src/database/dbOperations.js +105 -0
  24. package/src/handlers/activity/activityGetMe.js +98 -0
  25. package/src/handlers/activity/activityGetTeam.js +130 -0
  26. package/src/handlers/alerts/alertsAcknowledge.js +91 -0
  27. package/src/handlers/alerts/alertsGet.js +250 -0
  28. package/src/handlers/collaborators/collaboratorAdd.js +201 -0
  29. package/src/handlers/collaborators/collaboratorInvite.js +218 -0
  30. package/src/handlers/collaborators/collaboratorList.js +88 -0
  31. package/src/handlers/collaborators/collaboratorRemove.js +127 -0
  32. package/src/handlers/collaborators/inviteAccept.js +122 -0
  33. package/src/handlers/context/contextGet.js +57 -0
  34. package/src/handlers/context/invariantsGet.js +74 -0
  35. package/src/handlers/context/loopsGet.js +82 -0
  36. package/src/handlers/context/notesCreate.js +74 -0
  37. package/src/handlers/context/purposeGet.js +78 -0
  38. package/src/handlers/correlations/correlationsDeveloperGet.js +226 -0
  39. package/src/handlers/correlations/correlationsGet.js +93 -0
  40. package/src/handlers/correlations/correlationsProjectGet.js +161 -0
  41. package/src/handlers/github/githubConnectionStatus.js +49 -0
  42. package/src/handlers/github/githubDiscoverPatterns.js +364 -0
  43. package/src/handlers/github/githubOAuthCallback.js +166 -0
  44. package/src/handlers/github/githubOAuthStart.js +59 -0
  45. package/src/handlers/github/githubPatternsReview.js +109 -0
  46. package/src/handlers/github/githubReposList.js +105 -0
  47. package/src/handlers/helpers/checkSuperAdmin.js +85 -0
  48. package/src/handlers/helpers/dbOperations.js +53 -0
  49. package/src/handlers/helpers/errorHandler.js +49 -0
  50. package/src/handlers/helpers/index.js +106 -0
  51. package/src/handlers/helpers/lambdaWrapper.js +60 -0
  52. package/src/handlers/helpers/responseUtil.js +55 -0
  53. package/src/handlers/helpers/subscriptionTiers.js +1168 -0
  54. package/src/handlers/notifications/getPreferences.js +84 -0
  55. package/src/handlers/notifications/sendNotification.js +170 -0
  56. package/src/handlers/notifications/updatePreferences.js +316 -0
  57. package/src/handlers/patterns/patternUsagePost.js +182 -0
  58. package/src/handlers/patterns/patternViolationPost.js +185 -0
  59. package/src/handlers/projects/projectCreate.js +107 -0
  60. package/src/handlers/projects/projectDelete.js +82 -0
  61. package/src/handlers/projects/projectGet.js +95 -0
  62. package/src/handlers/projects/projectUpdate.js +118 -0
  63. package/src/handlers/reports/aiLeverage.js +206 -0
  64. package/src/handlers/reports/engineeringInvestment.js +132 -0
  65. package/src/handlers/reports/riskForecast.js +186 -0
  66. package/src/handlers/reports/standardsRoi.js +162 -0
  67. package/src/handlers/scheduled/analyzeCorrelations.js +178 -0
  68. package/src/handlers/scheduled/analyzeGitHistory.js +510 -0
  69. package/src/handlers/scheduled/generateAlerts.js +135 -0
  70. package/src/handlers/scheduled/refreshActivity.js +21 -0
  71. package/src/handlers/scheduled/scanCompliance.js +334 -0
  72. package/src/handlers/sessions/sessionEndPost.js +180 -0
  73. package/src/handlers/sessions/sessionStandardsPost.js +135 -0
  74. package/src/handlers/stripe/addonManagePost.js +240 -0
  75. package/src/handlers/stripe/billingPortalPost.js +93 -0
  76. package/src/handlers/stripe/enterpriseCheckoutPost.js +272 -0
  77. package/src/handlers/stripe/seatsUpdatePost.js +185 -0
  78. package/src/handlers/stripe/subscriptionCancelDelete.js +169 -0
  79. package/src/handlers/stripe/subscriptionCreatePost.js +221 -0
  80. package/src/handlers/stripe/subscriptionUpdatePut.js +163 -0
  81. package/src/handlers/stripe/webhookPost.js +454 -0
  82. package/src/handlers/users/cognitoPostConfirmation.js +150 -0
  83. package/src/handlers/users/userEntitlementsGet.js +89 -0
  84. package/src/handlers/users/userGet.js +114 -0
  85. package/src/handlers/webhooks/githubWebhook.js +223 -0
  86. package/src/index.js +969 -0
@@ -0,0 +1,460 @@
1
+ /**
2
+ * Collaboration Prompt UX
3
+ *
4
+ * Prompts user when new project detected:
5
+ * - Collaborate with others vs Keep private
6
+ * - Discover collaborators from git/CODEOWNERS
7
+ * - Setup project-scoped memory
8
+ *
9
+ * Part of Rapport v3 project detection flow.
10
+ */
11
+
12
+ const { exec } = require('child_process');
13
+ const { promisify } = require('util');
14
+ const fs = require('fs').promises;
15
+ const path = require('path');
16
+
17
+ const execAsync = promisify(exec);
18
+
19
+ class CollaborationPrompt {
20
+ constructor(config = {}) {
21
+ this.config = {
22
+ minCommitsToInclude: config.minCommitsToInclude || 3,
23
+ maxCollaborators: config.maxCollaborators || 10,
24
+ ...config
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Prompt user for collaboration preferences
30
+ *
31
+ * Returns: { collaborate, collaborators, private }
32
+ */
33
+ async prompt(projectPath, projectName) {
34
+ console.log('\n┌─────────────────────────────────────────┐');
35
+ console.log(`│ New project detected: ${projectName.padEnd(18)} │`);
36
+ console.log('│ │');
37
+ console.log('│ Would you like to: │');
38
+ console.log('│ 1. Collaborate with others │');
39
+ console.log('│ 2. Keep private (solo) │');
40
+ console.log('└─────────────────────────────────────────┘\n');
41
+
42
+ const choice = await this.getUserChoice(['1', '2']);
43
+
44
+ if (choice === '2') {
45
+ // Private project
46
+ return {
47
+ collaborate: false,
48
+ collaborators: [],
49
+ private: true
50
+ };
51
+ }
52
+
53
+ // Discover collaborators
54
+ const discovered = await this.discoverCollaborators(projectPath);
55
+
56
+ if (discovered.length === 0) {
57
+ console.log('\nNo collaborators detected from git history.');
58
+ console.log('Starting as solo project (you can add collaborators later).\n');
59
+
60
+ return {
61
+ collaborate: false,
62
+ collaborators: [],
63
+ private: false
64
+ };
65
+ }
66
+
67
+ // Show discovered collaborators
68
+ console.log('\n┌─────────────────────────────────────────┐');
69
+ console.log(`│ Who's working on ${projectName.padEnd(20)} │`);
70
+ console.log('│ │');
71
+ console.log('│ Detected from git history: │');
72
+
73
+ for (const collab of discovered) {
74
+ const line = `│ ✓ ${collab.name.padEnd(25)} (${collab.commits} commits)`;
75
+ console.log(line.padEnd(42) + '│');
76
+ }
77
+
78
+ console.log('│ │');
79
+ console.log('│ Include all? (y/n) │');
80
+ console.log('└─────────────────────────────────────────┘\n');
81
+
82
+ const includeAll = await this.getUserChoice(['y', 'n']);
83
+
84
+ let collaborators = discovered;
85
+
86
+ if (includeAll === 'n') {
87
+ // Let user select which ones to include
88
+ collaborators = await this.selectCollaborators(discovered);
89
+ }
90
+
91
+ // Ask if they want to add more
92
+ console.log('\nAdd more collaborators? (y/n)');
93
+ const addMore = await this.getUserChoice(['y', 'n']);
94
+
95
+ if (addMore === 'y') {
96
+ const additional = await this.promptForAdditional();
97
+ collaborators = [...collaborators, ...additional];
98
+ }
99
+
100
+ return {
101
+ collaborate: true,
102
+ collaborators,
103
+ private: false
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Discover collaborators from git history
109
+ */
110
+ async discoverCollaborators(projectPath) {
111
+ const collaborators = [];
112
+
113
+ try {
114
+ // Get git contributors
115
+ const gitContributors = await this.getGitContributors(projectPath);
116
+ collaborators.push(...gitContributors);
117
+
118
+ // Get from CODEOWNERS
119
+ const codeowners = await this.parseCodeowners(projectPath);
120
+ collaborators.push(...codeowners);
121
+
122
+ // Get from package.json
123
+ const pkgContributors = await this.getPackageContributors(projectPath);
124
+ collaborators.push(...pkgContributors);
125
+
126
+ // Deduplicate by email
127
+ return this.deduplicateCollaborators(collaborators);
128
+
129
+ } catch (error) {
130
+ console.error('Error discovering collaborators:', error.message);
131
+ return [];
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Get contributors from git history
137
+ */
138
+ async getGitContributors(projectPath) {
139
+ try {
140
+ const { stdout } = await execAsync(
141
+ 'git log --format="%an|%ae" | sort | uniq -c | sort -rn',
142
+ { cwd: projectPath }
143
+ );
144
+
145
+ const contributors = [];
146
+ const lines = stdout.trim().split('\n');
147
+
148
+ for (const line of lines) {
149
+ const match = line.trim().match(/^\s*(\d+)\s+(.+)\|(.+)$/);
150
+ if (match) {
151
+ const [, commits, name, email] = match;
152
+ const commitCount = parseInt(commits);
153
+
154
+ if (commitCount >= this.config.minCommitsToInclude) {
155
+ contributors.push({
156
+ name: name.trim(),
157
+ email: email.trim(),
158
+ commits: commitCount,
159
+ source: 'git'
160
+ });
161
+ }
162
+ }
163
+ }
164
+
165
+ return contributors;
166
+
167
+ } catch (error) {
168
+ // Not a git repo or git not available
169
+ return [];
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Parse CODEOWNERS file
175
+ */
176
+ async parseCodeowners(projectPath) {
177
+ const codeownersPath = path.join(projectPath, 'CODEOWNERS');
178
+
179
+ try {
180
+ const content = await fs.readFile(codeownersPath, 'utf-8');
181
+ const collaborators = [];
182
+
183
+ const lines = content.split('\n');
184
+ for (const line of lines) {
185
+ // Skip comments and empty lines
186
+ if (line.trim().startsWith('#') || !line.trim()) {
187
+ continue;
188
+ }
189
+
190
+ // Extract GitHub handles or emails
191
+ const handles = line.match(/@[\w-]+/g);
192
+ if (handles) {
193
+ for (const handle of handles) {
194
+ collaborators.push({
195
+ name: handle.substring(1), // Remove @
196
+ email: `${handle.substring(1)}@github.com`,
197
+ source: 'CODEOWNERS'
198
+ });
199
+ }
200
+ }
201
+
202
+ const emails = line.match(/[\w.-]+@[\w.-]+/g);
203
+ if (emails) {
204
+ for (const email of emails) {
205
+ collaborators.push({
206
+ name: email.split('@')[0],
207
+ email,
208
+ source: 'CODEOWNERS'
209
+ });
210
+ }
211
+ }
212
+ }
213
+
214
+ return collaborators;
215
+
216
+ } catch (error) {
217
+ // No CODEOWNERS file
218
+ return [];
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Get contributors from package.json
224
+ */
225
+ async getPackageContributors(projectPath) {
226
+ const packagePath = path.join(projectPath, 'package.json');
227
+
228
+ try {
229
+ const content = await fs.readFile(packagePath, 'utf-8');
230
+ const pkg = JSON.parse(content);
231
+ const collaborators = [];
232
+
233
+ // Author
234
+ if (pkg.author) {
235
+ const author = typeof pkg.author === 'string'
236
+ ? this.parseContributor(pkg.author)
237
+ : pkg.author;
238
+
239
+ if (author) {
240
+ collaborators.push({
241
+ ...author,
242
+ source: 'package.json'
243
+ });
244
+ }
245
+ }
246
+
247
+ // Contributors
248
+ if (Array.isArray(pkg.contributors)) {
249
+ for (const contrib of pkg.contributors) {
250
+ const parsed = typeof contrib === 'string'
251
+ ? this.parseContributor(contrib)
252
+ : contrib;
253
+
254
+ if (parsed) {
255
+ collaborators.push({
256
+ ...parsed,
257
+ source: 'package.json'
258
+ });
259
+ }
260
+ }
261
+ }
262
+
263
+ return collaborators;
264
+
265
+ } catch (error) {
266
+ // No package.json or parse error
267
+ return [];
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Parse contributor string
273
+ * Examples: "John Doe <john@example.com>", "john@example.com"
274
+ */
275
+ parseContributor(str) {
276
+ const match = str.match(/^([^<]+?)\s*<([^>]+)>$/);
277
+
278
+ if (match) {
279
+ return {
280
+ name: match[1].trim(),
281
+ email: match[2].trim()
282
+ };
283
+ }
284
+
285
+ // Just email
286
+ if (str.includes('@')) {
287
+ return {
288
+ name: str.split('@')[0],
289
+ email: str.trim()
290
+ };
291
+ }
292
+
293
+ return null;
294
+ }
295
+
296
+ /**
297
+ * Deduplicate collaborators by email
298
+ */
299
+ deduplicateCollaborators(collaborators) {
300
+ const seen = new Map();
301
+
302
+ for (const collab of collaborators) {
303
+ if (!collab.email) continue;
304
+
305
+ const email = collab.email.toLowerCase();
306
+
307
+ if (!seen.has(email)) {
308
+ seen.set(email, collab);
309
+ } else {
310
+ // Merge commit counts if available
311
+ const existing = seen.get(email);
312
+ if (collab.commits) {
313
+ existing.commits = (existing.commits || 0) + collab.commits;
314
+ }
315
+ }
316
+ }
317
+
318
+ return Array.from(seen.values())
319
+ .sort((a, b) => (b.commits || 0) - (a.commits || 0))
320
+ .slice(0, this.config.maxCollaborators);
321
+ }
322
+
323
+ /**
324
+ * Let user select specific collaborators
325
+ */
326
+ async selectCollaborators(discovered) {
327
+ const selected = [];
328
+
329
+ console.log('\nSelect collaborators to include:\n');
330
+
331
+ for (let i = 0; i < discovered.length; i++) {
332
+ const collab = discovered[i];
333
+ console.log(`${i + 1}. ${collab.name} (${collab.email})`);
334
+ }
335
+
336
+ console.log('\nEnter numbers separated by commas (e.g., 1,2,3):');
337
+
338
+ const input = await this.getUserInput();
339
+ const indices = input.split(',').map(s => parseInt(s.trim()) - 1);
340
+
341
+ for (const index of indices) {
342
+ if (index >= 0 && index < discovered.length) {
343
+ selected.push(discovered[index]);
344
+ }
345
+ }
346
+
347
+ return selected;
348
+ }
349
+
350
+ /**
351
+ * Prompt for additional collaborators
352
+ */
353
+ async promptForAdditional() {
354
+ const additional = [];
355
+
356
+ console.log('\nEnter collaborator details:');
357
+
358
+ while (true) {
359
+ console.log('\nName:');
360
+ const name = await this.getUserInput();
361
+
362
+ console.log('Email:');
363
+ const email = await this.getUserInput();
364
+
365
+ if (name && email) {
366
+ additional.push({
367
+ name: name.trim(),
368
+ email: email.trim(),
369
+ source: 'manual'
370
+ });
371
+ }
372
+
373
+ console.log('\nAdd another? (y/n)');
374
+ const addAnother = await this.getUserChoice(['y', 'n']);
375
+
376
+ if (addAnother === 'n') {
377
+ break;
378
+ }
379
+ }
380
+
381
+ return additional;
382
+ }
383
+
384
+ /**
385
+ * Get user choice (single character)
386
+ */
387
+ async getUserChoice(validChoices) {
388
+ return new Promise((resolve) => {
389
+ process.stdin.once('data', (data) => {
390
+ const choice = data.toString().trim().toLowerCase();
391
+
392
+ if (validChoices.includes(choice)) {
393
+ resolve(choice);
394
+ } else {
395
+ console.log(`Invalid choice. Please enter one of: ${validChoices.join(', ')}`);
396
+ this.getUserChoice(validChoices).then(resolve);
397
+ }
398
+ });
399
+ });
400
+ }
401
+
402
+ /**
403
+ * Get user input (full line)
404
+ */
405
+ async getUserInput() {
406
+ return new Promise((resolve) => {
407
+ process.stdin.once('data', (data) => {
408
+ resolve(data.toString().trim());
409
+ });
410
+ });
411
+ }
412
+
413
+ /**
414
+ * Format collaborators for display
415
+ */
416
+ formatCollaborators(collaborators) {
417
+ return collaborators.map(c => ({
418
+ userId: this.generateUserId(c.email),
419
+ name: c.name,
420
+ email: c.email,
421
+ role: 'collaborator',
422
+ source: c.source,
423
+ commits: c.commits
424
+ }));
425
+ }
426
+
427
+ /**
428
+ * Generate user ID from email
429
+ */
430
+ generateUserId(email) {
431
+ // Simple: use email prefix
432
+ return email.split('@')[0].toLowerCase().replace(/[^a-z0-9]/g, '');
433
+ }
434
+
435
+ /**
436
+ * Create project configuration
437
+ */
438
+ createProjectConfig(projectName, collaborators, options = {}) {
439
+ return {
440
+ projectId: this.generateProjectId(projectName),
441
+ projectName,
442
+ created: new Date().toISOString(),
443
+ collaborators: this.formatCollaborators(collaborators),
444
+ private: options.private || false,
445
+ externalUsersAllowed: options.externalUsersAllowed || false
446
+ };
447
+ }
448
+
449
+ /**
450
+ * Generate project ID from name
451
+ */
452
+ generateProjectId(projectName) {
453
+ return projectName
454
+ .toLowerCase()
455
+ .replace(/[^a-z0-9]+/g, '-')
456
+ .replace(/^-|-$/g, '');
457
+ }
458
+ }
459
+
460
+ module.exports = CollaborationPrompt;