@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.
- package/README.md +300 -0
- package/hooks/README.md +494 -0
- package/hooks/pre-compact.js +392 -0
- package/hooks/session-start.js +264 -0
- package/package.json +90 -0
- package/scripts/harvest.js +561 -0
- package/scripts/init-project.js +437 -0
- package/scripts/inject.js +388 -0
- package/src/collaboration/CollaborationPrompt.js +460 -0
- package/src/core/AlertEngine.js +813 -0
- package/src/core/AlertNotifier.js +363 -0
- package/src/core/CorrelationAnalyzer.js +774 -0
- package/src/core/CurationEngine.js +688 -0
- package/src/core/LLMPatternDetector.js +508 -0
- package/src/core/LoadBearingDetector.js +242 -0
- package/src/core/NotificationService.js +1032 -0
- package/src/core/PatternValidator.js +355 -0
- package/src/core/README.md +160 -0
- package/src/core/RapportOrchestrator.js +446 -0
- package/src/core/RelevanceDetector.js +577 -0
- package/src/core/StandardsIngestion.js +575 -0
- package/src/core/TeamLoadBearingDetector.js +431 -0
- package/src/database/dbOperations.js +105 -0
- package/src/handlers/activity/activityGetMe.js +98 -0
- package/src/handlers/activity/activityGetTeam.js +130 -0
- package/src/handlers/alerts/alertsAcknowledge.js +91 -0
- package/src/handlers/alerts/alertsGet.js +250 -0
- package/src/handlers/collaborators/collaboratorAdd.js +201 -0
- package/src/handlers/collaborators/collaboratorInvite.js +218 -0
- package/src/handlers/collaborators/collaboratorList.js +88 -0
- package/src/handlers/collaborators/collaboratorRemove.js +127 -0
- package/src/handlers/collaborators/inviteAccept.js +122 -0
- package/src/handlers/context/contextGet.js +57 -0
- package/src/handlers/context/invariantsGet.js +74 -0
- package/src/handlers/context/loopsGet.js +82 -0
- package/src/handlers/context/notesCreate.js +74 -0
- package/src/handlers/context/purposeGet.js +78 -0
- package/src/handlers/correlations/correlationsDeveloperGet.js +226 -0
- package/src/handlers/correlations/correlationsGet.js +93 -0
- package/src/handlers/correlations/correlationsProjectGet.js +161 -0
- package/src/handlers/github/githubConnectionStatus.js +49 -0
- package/src/handlers/github/githubDiscoverPatterns.js +364 -0
- package/src/handlers/github/githubOAuthCallback.js +166 -0
- package/src/handlers/github/githubOAuthStart.js +59 -0
- package/src/handlers/github/githubPatternsReview.js +109 -0
- package/src/handlers/github/githubReposList.js +105 -0
- package/src/handlers/helpers/checkSuperAdmin.js +85 -0
- package/src/handlers/helpers/dbOperations.js +53 -0
- package/src/handlers/helpers/errorHandler.js +49 -0
- package/src/handlers/helpers/index.js +106 -0
- package/src/handlers/helpers/lambdaWrapper.js +60 -0
- package/src/handlers/helpers/responseUtil.js +55 -0
- package/src/handlers/helpers/subscriptionTiers.js +1168 -0
- package/src/handlers/notifications/getPreferences.js +84 -0
- package/src/handlers/notifications/sendNotification.js +170 -0
- package/src/handlers/notifications/updatePreferences.js +316 -0
- package/src/handlers/patterns/patternUsagePost.js +182 -0
- package/src/handlers/patterns/patternViolationPost.js +185 -0
- package/src/handlers/projects/projectCreate.js +107 -0
- package/src/handlers/projects/projectDelete.js +82 -0
- package/src/handlers/projects/projectGet.js +95 -0
- package/src/handlers/projects/projectUpdate.js +118 -0
- package/src/handlers/reports/aiLeverage.js +206 -0
- package/src/handlers/reports/engineeringInvestment.js +132 -0
- package/src/handlers/reports/riskForecast.js +186 -0
- package/src/handlers/reports/standardsRoi.js +162 -0
- package/src/handlers/scheduled/analyzeCorrelations.js +178 -0
- package/src/handlers/scheduled/analyzeGitHistory.js +510 -0
- package/src/handlers/scheduled/generateAlerts.js +135 -0
- package/src/handlers/scheduled/refreshActivity.js +21 -0
- package/src/handlers/scheduled/scanCompliance.js +334 -0
- package/src/handlers/sessions/sessionEndPost.js +180 -0
- package/src/handlers/sessions/sessionStandardsPost.js +135 -0
- package/src/handlers/stripe/addonManagePost.js +240 -0
- package/src/handlers/stripe/billingPortalPost.js +93 -0
- package/src/handlers/stripe/enterpriseCheckoutPost.js +272 -0
- package/src/handlers/stripe/seatsUpdatePost.js +185 -0
- package/src/handlers/stripe/subscriptionCancelDelete.js +169 -0
- package/src/handlers/stripe/subscriptionCreatePost.js +221 -0
- package/src/handlers/stripe/subscriptionUpdatePut.js +163 -0
- package/src/handlers/stripe/webhookPost.js +454 -0
- package/src/handlers/users/cognitoPostConfirmation.js +150 -0
- package/src/handlers/users/userEntitlementsGet.js +89 -0
- package/src/handlers/users/userGet.js +114 -0
- package/src/handlers/webhooks/githubWebhook.js +223 -0
- 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;
|