@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
package/src/index.js
ADDED
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindMeld - mindmeld.dev
|
|
3
|
+
*
|
|
4
|
+
* Intelligent standards injection for AI coding sessions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const TeamLoadBearingDetector = require('./core/TeamLoadBearingDetector');
|
|
8
|
+
const CollaborationPrompt = require('./collaboration/CollaborationPrompt');
|
|
9
|
+
const { RelevanceDetector } = require('./core/RelevanceDetector');
|
|
10
|
+
const { PatternValidator } = require('./core/PatternValidator');
|
|
11
|
+
const { StandardsIngestion } = require('./core/StandardsIngestion');
|
|
12
|
+
|
|
13
|
+
class MindmeldClient {
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
this.config = {
|
|
16
|
+
projectPath: config.projectPath || process.cwd(),
|
|
17
|
+
userId: config.userId || process.env.USER || 'unknown',
|
|
18
|
+
apiUrl: config.apiUrl || process.env.MINDMELD_API_URL || 'https://api.mindmeld.dev',
|
|
19
|
+
authToken: config.authToken || process.env.MINDMELD_AUTH_TOKEN || null,
|
|
20
|
+
companyId: config.companyId || process.env.MINDMELD_COMPANY_ID || null,
|
|
21
|
+
standardsPath: config.standardsPath || null,
|
|
22
|
+
...config
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Initialize components
|
|
26
|
+
this.teamDetector = new TeamLoadBearingDetector();
|
|
27
|
+
this.collaborationPrompt = new CollaborationPrompt();
|
|
28
|
+
|
|
29
|
+
// Phase 2-5 components (Claude Code integration)
|
|
30
|
+
this.relevanceDetector = new RelevanceDetector({
|
|
31
|
+
workingDirectory: this.config.projectPath,
|
|
32
|
+
standardsPath: this.config.standardsPath
|
|
33
|
+
});
|
|
34
|
+
this.patternValidator = new PatternValidator();
|
|
35
|
+
this.standardsIngestion = new StandardsIngestion({
|
|
36
|
+
standardsPath: this.config.standardsPath
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Project context
|
|
40
|
+
this.currentProject = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Detect and initialize project
|
|
45
|
+
*/
|
|
46
|
+
async detectProject() {
|
|
47
|
+
const projectName = this.getProjectName();
|
|
48
|
+
|
|
49
|
+
// Check if project already registered
|
|
50
|
+
const existing = await this.getProject(projectName);
|
|
51
|
+
|
|
52
|
+
if (existing) {
|
|
53
|
+
this.currentProject = existing;
|
|
54
|
+
return existing;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// New project - prompt for collaboration
|
|
58
|
+
const result = await this.collaborationPrompt.prompt(
|
|
59
|
+
this.config.projectPath,
|
|
60
|
+
projectName
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (result.collaborate) {
|
|
64
|
+
// Initialize collaborative project
|
|
65
|
+
this.currentProject = await this.initializeProject(projectName, {
|
|
66
|
+
collaborators: result.collaborators,
|
|
67
|
+
private: result.private
|
|
68
|
+
});
|
|
69
|
+
} else {
|
|
70
|
+
// Initialize solo project
|
|
71
|
+
this.currentProject = await this.initializeProject(projectName, {
|
|
72
|
+
collaborators: [],
|
|
73
|
+
private: true
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return this.currentProject;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get project name from directory
|
|
82
|
+
*/
|
|
83
|
+
getProjectName() {
|
|
84
|
+
const path = require('path');
|
|
85
|
+
return path.basename(this.config.projectPath);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get existing project by name or ID
|
|
90
|
+
* Fetches project from API if authenticated, falls back to local storage
|
|
91
|
+
* @param {string} projectNameOrId - Project name or project ID
|
|
92
|
+
* @returns {Promise<Object|null>} Project object or null if not found
|
|
93
|
+
*/
|
|
94
|
+
async getProject(projectNameOrId) {
|
|
95
|
+
// Try API first if authenticated
|
|
96
|
+
if (this.config.authToken) {
|
|
97
|
+
try {
|
|
98
|
+
const apiUrl = this.config.apiUrl;
|
|
99
|
+
const companyId = this.config.companyId;
|
|
100
|
+
|
|
101
|
+
// Build query params
|
|
102
|
+
const params = new URLSearchParams();
|
|
103
|
+
if (companyId) {
|
|
104
|
+
params.append('Company_ID', companyId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const response = await this._makeApiRequest(
|
|
108
|
+
`${apiUrl}/api/projects?${params.toString()}`,
|
|
109
|
+
'GET'
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (response.error) {
|
|
113
|
+
console.error('[Rapport] getProject API error:', response.error);
|
|
114
|
+
// Fall through to local storage
|
|
115
|
+
} else if (response.Records) {
|
|
116
|
+
// Search for project by name or ID
|
|
117
|
+
const project = response.Records.find(
|
|
118
|
+
p => p.project_name === projectNameOrId || p.project_id === projectNameOrId
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (project) {
|
|
122
|
+
return {
|
|
123
|
+
projectId: project.project_id,
|
|
124
|
+
projectName: project.project_name,
|
|
125
|
+
companyId: project.company_id,
|
|
126
|
+
description: project.description,
|
|
127
|
+
private: project.private,
|
|
128
|
+
collaborators: project.collaborators || [],
|
|
129
|
+
createdAt: project.created_at,
|
|
130
|
+
lastActive: project.last_active
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error('[Rapport] getProject API call failed:', error.message);
|
|
136
|
+
// Fall through to local storage
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Fallback: check local storage
|
|
141
|
+
const fs = require('fs').promises;
|
|
142
|
+
const path = require('path');
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
// Try to find project in local projects directory
|
|
146
|
+
const projectsDir = path.join(__dirname, '../projects');
|
|
147
|
+
const dirs = await fs.readdir(projectsDir).catch(() => []);
|
|
148
|
+
|
|
149
|
+
for (const dir of dirs) {
|
|
150
|
+
const configPath = path.join(projectsDir, dir, 'config.json');
|
|
151
|
+
try {
|
|
152
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
153
|
+
const project = JSON.parse(content);
|
|
154
|
+
if (project.projectName === projectNameOrId || project.projectId === projectNameOrId) {
|
|
155
|
+
return project;
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
// Skip invalid config files
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
// Local storage not available
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Initialize new project
|
|
170
|
+
* Creates project via API if authenticated, stores locally as fallback
|
|
171
|
+
* @param {string} projectName - Name of the project
|
|
172
|
+
* @param {Object} options - Project options
|
|
173
|
+
* @param {Array} options.collaborators - List of collaborator emails
|
|
174
|
+
* @param {boolean} options.private - Whether project is private
|
|
175
|
+
* @param {string} options.description - Project description
|
|
176
|
+
* @param {string} options.repoUrl - Repository URL
|
|
177
|
+
* @returns {Promise<Object>} Created project object
|
|
178
|
+
*/
|
|
179
|
+
async initializeProject(projectName, options = {}) {
|
|
180
|
+
const project = this.collaborationPrompt.createProjectConfig(
|
|
181
|
+
projectName,
|
|
182
|
+
options.collaborators || [],
|
|
183
|
+
{
|
|
184
|
+
private: options.private || false
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Try API first if authenticated
|
|
189
|
+
if (this.config.authToken && this.config.companyId) {
|
|
190
|
+
try {
|
|
191
|
+
const apiUrl = this.config.apiUrl;
|
|
192
|
+
|
|
193
|
+
// Build request payload matching projectCreate handler
|
|
194
|
+
const payload = {
|
|
195
|
+
Company_ID: this.config.companyId,
|
|
196
|
+
project_name: projectName,
|
|
197
|
+
description: options.description || `Project for ${projectName}`,
|
|
198
|
+
private: options.private || false,
|
|
199
|
+
repo_url: options.repoUrl || null
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const response = await this._makeApiRequest(
|
|
203
|
+
`${apiUrl}/api/projects`,
|
|
204
|
+
'POST',
|
|
205
|
+
payload
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (response.error) {
|
|
209
|
+
// Handle specific error cases
|
|
210
|
+
if (response.status === 409) {
|
|
211
|
+
console.log('[Rapport] Project already exists, fetching existing...');
|
|
212
|
+
const existing = await this.getProject(projectName);
|
|
213
|
+
if (existing) return existing;
|
|
214
|
+
}
|
|
215
|
+
console.error('[Rapport] initializeProject API error:', response.error);
|
|
216
|
+
// Fall through to local storage
|
|
217
|
+
} else if (response.Records && response.Records.length > 0) {
|
|
218
|
+
const created = response.Records[0];
|
|
219
|
+
|
|
220
|
+
// Add collaborators if specified
|
|
221
|
+
if (options.collaborators && options.collaborators.length > 0) {
|
|
222
|
+
for (const email of options.collaborators) {
|
|
223
|
+
await this._addCollaborator(created.project_id, email);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
projectId: created.project_id,
|
|
229
|
+
projectName: created.project_name,
|
|
230
|
+
companyId: created.company_id,
|
|
231
|
+
description: created.description,
|
|
232
|
+
private: created.private,
|
|
233
|
+
collaborators: options.collaborators || [],
|
|
234
|
+
createdAt: created.created_at
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error('[Rapport] initializeProject API call failed:', error.message);
|
|
239
|
+
// Fall through to local storage
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Fallback: store locally
|
|
244
|
+
await this.storeProjectLocally(project);
|
|
245
|
+
return project;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Add collaborator to project (internal helper)
|
|
250
|
+
* @private
|
|
251
|
+
*/
|
|
252
|
+
async _addCollaborator(projectId, email, role = 'collaborator') {
|
|
253
|
+
try {
|
|
254
|
+
const apiUrl = this.config.apiUrl;
|
|
255
|
+
await this._makeApiRequest(
|
|
256
|
+
`${apiUrl}/api/projects/${projectId}/collaborators`,
|
|
257
|
+
'POST',
|
|
258
|
+
{ email, role }
|
|
259
|
+
);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error(`[Rapport] Failed to add collaborator ${email}:`, error.message);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Store project configuration locally
|
|
267
|
+
*/
|
|
268
|
+
async storeProjectLocally(project) {
|
|
269
|
+
const fs = require('fs').promises;
|
|
270
|
+
const path = require('path');
|
|
271
|
+
|
|
272
|
+
const projectDir = path.join(__dirname, '../projects', project.projectId);
|
|
273
|
+
await fs.mkdir(projectDir, { recursive: true });
|
|
274
|
+
|
|
275
|
+
const configPath = path.join(projectDir, 'config.json');
|
|
276
|
+
await fs.writeFile(configPath, JSON.stringify(project, null, 2));
|
|
277
|
+
|
|
278
|
+
// Create collaborators file
|
|
279
|
+
const collabPath = path.join(projectDir, 'collaborators.json');
|
|
280
|
+
await fs.writeFile(collabPath, JSON.stringify({
|
|
281
|
+
projectId: project.projectId,
|
|
282
|
+
collaborators: project.collaborators,
|
|
283
|
+
updated: new Date().toISOString()
|
|
284
|
+
}, null, 2));
|
|
285
|
+
|
|
286
|
+
// Create sessions directory
|
|
287
|
+
const sessionsDir = path.join(projectDir, 'sessions');
|
|
288
|
+
await fs.mkdir(sessionsDir, { recursive: true });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Load project context including team patterns, load-bearing elements, and recent learning
|
|
293
|
+
* Fetches from API if authenticated, falls back to local storage
|
|
294
|
+
* @param {string} projectId - Project ID to load context for
|
|
295
|
+
* @param {Object} options - Context loading options
|
|
296
|
+
* @param {string} options.userId - User ID for personalized context
|
|
297
|
+
* @param {number} options.patternLimit - Max patterns to return (default 20)
|
|
298
|
+
* @param {number} options.learningDays - Days of recent learning to include (default 7)
|
|
299
|
+
* @returns {Promise<Object|null>} Project context or null if not found
|
|
300
|
+
*/
|
|
301
|
+
async loadProjectContext(projectId, options = {}) {
|
|
302
|
+
const userId = options.userId || this.config.userId;
|
|
303
|
+
const patternLimit = options.patternLimit || 20;
|
|
304
|
+
const learningDays = options.learningDays || 7;
|
|
305
|
+
|
|
306
|
+
// Try API first if authenticated
|
|
307
|
+
if (this.config.authToken) {
|
|
308
|
+
try {
|
|
309
|
+
const apiUrl = this.config.apiUrl;
|
|
310
|
+
|
|
311
|
+
// Build query params
|
|
312
|
+
const params = new URLSearchParams();
|
|
313
|
+
if (userId) params.append('userId', userId);
|
|
314
|
+
params.append('patternLimit', patternLimit.toString());
|
|
315
|
+
params.append('learningDays', learningDays.toString());
|
|
316
|
+
|
|
317
|
+
const response = await this._makeApiRequest(
|
|
318
|
+
`${apiUrl}/api/projects/${projectId}/context?${params.toString()}`,
|
|
319
|
+
'GET'
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (response.error) {
|
|
323
|
+
console.error('[Rapport] loadProjectContext API error:', response.error);
|
|
324
|
+
// Fall through to local storage
|
|
325
|
+
} else {
|
|
326
|
+
// Return normalized context object
|
|
327
|
+
return {
|
|
328
|
+
projectId: response.project_id || projectId,
|
|
329
|
+
projectName: response.project_name,
|
|
330
|
+
companyId: response.company_id,
|
|
331
|
+
|
|
332
|
+
// Team patterns (validated and reinforced)
|
|
333
|
+
patterns: (response.patterns || []).map(p => ({
|
|
334
|
+
patternId: p.pattern_id,
|
|
335
|
+
intent: p.intent,
|
|
336
|
+
element: p.element || p.intent,
|
|
337
|
+
maturity: p.maturity,
|
|
338
|
+
confidence: p.confidence || p.success_rate,
|
|
339
|
+
usageCount: p.usage_count || p.handoff_count,
|
|
340
|
+
lastUsed: p.last_used
|
|
341
|
+
})),
|
|
342
|
+
|
|
343
|
+
// Load-bearing context elements
|
|
344
|
+
loadBearingElements: (response.load_bearing_elements || []).map(e => ({
|
|
345
|
+
elementId: e.element_id,
|
|
346
|
+
type: e.element_type,
|
|
347
|
+
key: e.element_key,
|
|
348
|
+
value: e.element_value,
|
|
349
|
+
correlation: e.team_correlation,
|
|
350
|
+
confidence: e.confidence,
|
|
351
|
+
isLoadBearing: e.is_load_bearing
|
|
352
|
+
})),
|
|
353
|
+
|
|
354
|
+
// Recent team learning
|
|
355
|
+
recentLearning: (response.recent_learning || []).map(l => ({
|
|
356
|
+
patternId: l.pattern_id,
|
|
357
|
+
element: l.element,
|
|
358
|
+
learnedBy: l.discovered_by || l.email_address,
|
|
359
|
+
learnedAt: l.discovered_at || l.used_at,
|
|
360
|
+
success: l.success
|
|
361
|
+
})),
|
|
362
|
+
|
|
363
|
+
// Collaborators
|
|
364
|
+
collaborators: (response.collaborators || []).map(c => ({
|
|
365
|
+
email: c.email_address || c.email,
|
|
366
|
+
role: c.role,
|
|
367
|
+
isExternal: c.is_external,
|
|
368
|
+
lastActive: c.last_active
|
|
369
|
+
})),
|
|
370
|
+
|
|
371
|
+
// Standards applicable to this project
|
|
372
|
+
applicableStandards: response.applicable_standards || [],
|
|
373
|
+
|
|
374
|
+
// Metadata
|
|
375
|
+
lastActive: response.last_active,
|
|
376
|
+
sessionCount: response.session_count || 0
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error('[Rapport] loadProjectContext API call failed:', error.message);
|
|
381
|
+
// Fall through to local storage
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Fallback: load from local storage
|
|
386
|
+
const fs = require('fs').promises;
|
|
387
|
+
const path = require('path');
|
|
388
|
+
|
|
389
|
+
const configPath = path.join(__dirname, '../projects', projectId, 'config.json');
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
393
|
+
const localProject = JSON.parse(content);
|
|
394
|
+
|
|
395
|
+
// Return minimal local context
|
|
396
|
+
return {
|
|
397
|
+
projectId: localProject.projectId,
|
|
398
|
+
projectName: localProject.projectName,
|
|
399
|
+
companyId: localProject.companyId || null,
|
|
400
|
+
patterns: [],
|
|
401
|
+
loadBearingElements: [],
|
|
402
|
+
recentLearning: [],
|
|
403
|
+
collaborators: localProject.collaborators || [],
|
|
404
|
+
applicableStandards: [],
|
|
405
|
+
lastActive: localProject.lastActive || null,
|
|
406
|
+
sessionCount: 0
|
|
407
|
+
};
|
|
408
|
+
} catch (error) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Analyze handoff (team-wide)
|
|
415
|
+
*/
|
|
416
|
+
async analyzeHandoff(handoff, outcome) {
|
|
417
|
+
if (!this.currentProject) {
|
|
418
|
+
throw new Error('No project initialized. Call detectProject() first.');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return this.teamDetector.analyzeHandoff(
|
|
422
|
+
this.config.userId,
|
|
423
|
+
handoff,
|
|
424
|
+
outcome
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Get team load-bearing elements
|
|
430
|
+
*/
|
|
431
|
+
getTeamLoadBearing() {
|
|
432
|
+
return this.teamDetector.getTeamLoadBearing();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Get team summary
|
|
437
|
+
*/
|
|
438
|
+
getTeamSummary() {
|
|
439
|
+
return this.teamDetector.getTeamSummary();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ============================================================================
|
|
443
|
+
// Phase 5: Claude Code Hook Methods
|
|
444
|
+
// ============================================================================
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Ensure standards are ingested (cached check)
|
|
448
|
+
* Fast execution for session-start hook
|
|
449
|
+
*/
|
|
450
|
+
async ensureStandardsIngested() {
|
|
451
|
+
try {
|
|
452
|
+
const check = await this.standardsIngestion.needsIngestion();
|
|
453
|
+
|
|
454
|
+
if (check.needed) {
|
|
455
|
+
console.log(`[Rapport] Standards ingestion needed: ${check.reason}`);
|
|
456
|
+
await this.standardsIngestion.ingestEquilateralStandards();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return check;
|
|
460
|
+
} catch (error) {
|
|
461
|
+
console.error('[Rapport] ensureStandardsIngested error:', error.message);
|
|
462
|
+
return { needed: false, error: error.message };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Get relevant standards for current project context
|
|
468
|
+
* Used by session-start hook
|
|
469
|
+
*/
|
|
470
|
+
async getRelevantStandards(projectContext) {
|
|
471
|
+
try {
|
|
472
|
+
const context = {
|
|
473
|
+
workingDirectory: projectContext.workingDirectory || this.config.projectPath,
|
|
474
|
+
currentFile: projectContext.currentFile || null,
|
|
475
|
+
recentFiles: projectContext.recentFiles || [],
|
|
476
|
+
gitHistory: projectContext.gitHistory || null
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
return await this.relevanceDetector.detectRelevantStandards(context);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
console.error('[Rapport] getRelevantStandards error:', error.message);
|
|
482
|
+
return { standards: [], characteristics: {}, categories: [] };
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Get recent learning for project (last N days)
|
|
488
|
+
*/
|
|
489
|
+
async getRecentLearning(projectId, days = 7) {
|
|
490
|
+
try {
|
|
491
|
+
// TODO: Implement when pattern_usage table is populated
|
|
492
|
+
// For now return empty array
|
|
493
|
+
return [];
|
|
494
|
+
} catch (error) {
|
|
495
|
+
console.error('[Rapport] getRecentLearning error:', error.message);
|
|
496
|
+
return [];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Detect patterns from Claude Code session transcript
|
|
502
|
+
* Used by pre-compact hook
|
|
503
|
+
*/
|
|
504
|
+
async detectPatternsFromSession(sessionTranscript) {
|
|
505
|
+
try {
|
|
506
|
+
const patterns = [];
|
|
507
|
+
|
|
508
|
+
// Extract file changes from transcript
|
|
509
|
+
const files = this.extractFileChanges(sessionTranscript);
|
|
510
|
+
|
|
511
|
+
// Analyze each file for patterns
|
|
512
|
+
for (const file of files) {
|
|
513
|
+
const filePatterns = await this.extractPatternsFromFile(file);
|
|
514
|
+
patterns.push(...filePatterns);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return patterns;
|
|
518
|
+
} catch (error) {
|
|
519
|
+
console.error('[Rapport] detectPatternsFromSession error:', error.message);
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Validate pattern against standards
|
|
526
|
+
* Used by pre-compact hook
|
|
527
|
+
*/
|
|
528
|
+
async validatePattern(pattern) {
|
|
529
|
+
try {
|
|
530
|
+
return await this.patternValidator.validatePattern(pattern);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
console.error('[Rapport] validatePattern error:', error.message);
|
|
533
|
+
return { valid: true }; // Graceful degradation
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Record standards violation
|
|
539
|
+
* Stores violations in database for team learning and compliance tracking
|
|
540
|
+
* @param {Object} violationData - Violation data
|
|
541
|
+
* @param {Object} violationData.pattern - The pattern that violated standards
|
|
542
|
+
* @param {Array} violationData.violations - Array of violation details
|
|
543
|
+
* @param {string} violationData.sessionId - Session identifier
|
|
544
|
+
* @param {string} violationData.userId - User identifier
|
|
545
|
+
* @returns {Promise<Object>} Result indicating if violation was recorded
|
|
546
|
+
*/
|
|
547
|
+
async recordViolation(violationData) {
|
|
548
|
+
try {
|
|
549
|
+
const apiUrl = this.config.apiUrl;
|
|
550
|
+
|
|
551
|
+
// Build request payload
|
|
552
|
+
const payload = {
|
|
553
|
+
pattern: {
|
|
554
|
+
element: violationData.pattern.element,
|
|
555
|
+
type: violationData.pattern.type,
|
|
556
|
+
intent: violationData.pattern.intent || violationData.pattern.element,
|
|
557
|
+
file: violationData.pattern.file,
|
|
558
|
+
category: violationData.pattern.category
|
|
559
|
+
},
|
|
560
|
+
violations: violationData.violations.map(v => ({
|
|
561
|
+
standard_id: v.standardId || v.standard_id,
|
|
562
|
+
rule: v.rule,
|
|
563
|
+
description: v.description || v.reason
|
|
564
|
+
})),
|
|
565
|
+
session_id: violationData.sessionId,
|
|
566
|
+
user_id: violationData.userId,
|
|
567
|
+
project_id: this.currentProject?.projectId,
|
|
568
|
+
timestamp: new Date().toISOString()
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
const response = await this._makeApiRequest(
|
|
572
|
+
`${apiUrl}/api/patterns/violations`,
|
|
573
|
+
'POST',
|
|
574
|
+
payload
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
if (response.error) {
|
|
578
|
+
console.error('[Rapport] Violation recording failed:', response.error);
|
|
579
|
+
return { recorded: false, error: response.error };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
console.log('[Rapport] Violation recorded:', violationData.pattern.element);
|
|
583
|
+
return { recorded: true, violation_id: response.violation_id };
|
|
584
|
+
} catch (error) {
|
|
585
|
+
console.error('[Rapport] recordViolation error:', error.message);
|
|
586
|
+
return { recorded: false, error: error.message };
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Reinforce valid pattern
|
|
592
|
+
* Increments usage count in database for pattern validation tracking
|
|
593
|
+
* @param {Object} patternData - Pattern data
|
|
594
|
+
* @param {Object} patternData.pattern - The pattern to reinforce
|
|
595
|
+
* @param {string} patternData.sessionId - Session identifier
|
|
596
|
+
* @param {string} patternData.userId - User identifier
|
|
597
|
+
* @returns {Promise<Object>} Result indicating if pattern was reinforced
|
|
598
|
+
*/
|
|
599
|
+
async reinforcePattern(patternData) {
|
|
600
|
+
try {
|
|
601
|
+
const apiUrl = this.config.apiUrl;
|
|
602
|
+
|
|
603
|
+
// Build request payload
|
|
604
|
+
const payload = {
|
|
605
|
+
pattern: {
|
|
606
|
+
element: patternData.pattern.element,
|
|
607
|
+
type: patternData.pattern.type,
|
|
608
|
+
intent: patternData.pattern.intent || patternData.pattern.element,
|
|
609
|
+
file: patternData.pattern.file,
|
|
610
|
+
category: patternData.pattern.category,
|
|
611
|
+
confidence: patternData.pattern.confidence || 1.0,
|
|
612
|
+
evidence: patternData.pattern.evidence
|
|
613
|
+
},
|
|
614
|
+
session_id: patternData.sessionId,
|
|
615
|
+
user_id: patternData.userId,
|
|
616
|
+
project_id: this.currentProject?.projectId,
|
|
617
|
+
success: true,
|
|
618
|
+
context: {
|
|
619
|
+
working_directory: this.config.projectPath,
|
|
620
|
+
source: patternData.pattern.source || 'regex'
|
|
621
|
+
},
|
|
622
|
+
timestamp: new Date().toISOString()
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
const response = await this._makeApiRequest(
|
|
626
|
+
`${apiUrl}/api/patterns/usage`,
|
|
627
|
+
'POST',
|
|
628
|
+
payload
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
if (response.error) {
|
|
632
|
+
console.error('[Rapport] Pattern reinforcement failed:', response.error);
|
|
633
|
+
return { reinforced: false, error: response.error };
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
console.log('[Rapport] Pattern reinforced:', patternData.pattern.element);
|
|
637
|
+
return {
|
|
638
|
+
reinforced: true,
|
|
639
|
+
usage_id: response.usage_id,
|
|
640
|
+
usage_count: response.usage_count,
|
|
641
|
+
maturity: response.maturity
|
|
642
|
+
};
|
|
643
|
+
} catch (error) {
|
|
644
|
+
console.error('[Rapport] reinforcePattern error:', error.message);
|
|
645
|
+
return { reinforced: false, error: error.message };
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Evaluate pattern for promotion to standards
|
|
651
|
+
* Calls CurationEngine promotion logic via API
|
|
652
|
+
*
|
|
653
|
+
* Promotion Criteria (from CurationEngine):
|
|
654
|
+
* - correlation >= 0.90 (90% success rate)
|
|
655
|
+
* - projectCount >= 5 (proven across 5+ projects)
|
|
656
|
+
* - developerCount >= 3 (used by 3+ developers)
|
|
657
|
+
* - sessionCount >= 10 (reinforced 10+ times)
|
|
658
|
+
*
|
|
659
|
+
* @param {Object} pattern - Pattern to evaluate
|
|
660
|
+
* @returns {Promise<Object|null>} Curation candidate if eligible, null otherwise
|
|
661
|
+
*/
|
|
662
|
+
async evaluateForPromotion(pattern) {
|
|
663
|
+
try {
|
|
664
|
+
const apiUrl = this.config.apiUrl;
|
|
665
|
+
|
|
666
|
+
// Build request payload
|
|
667
|
+
const payload = {
|
|
668
|
+
pattern: {
|
|
669
|
+
element: pattern.element,
|
|
670
|
+
type: pattern.type,
|
|
671
|
+
intent: pattern.intent || pattern.element,
|
|
672
|
+
category: pattern.category,
|
|
673
|
+
file: pattern.file
|
|
674
|
+
},
|
|
675
|
+
project_id: this.currentProject?.projectId,
|
|
676
|
+
evaluate_only: true // Just check eligibility, don't create candidate yet
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
const response = await this._makeApiRequest(
|
|
680
|
+
`${apiUrl}/api/patterns/evaluate-promotion`,
|
|
681
|
+
'POST',
|
|
682
|
+
payload
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
if (response.error) {
|
|
686
|
+
// Not an error for promotion - pattern just doesn't meet criteria
|
|
687
|
+
if (response.status === 404 || response.reason === 'not_eligible') {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
console.error('[Rapport] Pattern promotion evaluation failed:', response.error);
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Pattern is eligible for promotion
|
|
695
|
+
if (response.eligible) {
|
|
696
|
+
console.log(`[Rapport] Pattern eligible for promotion: ${pattern.element}`);
|
|
697
|
+
return {
|
|
698
|
+
element: pattern.element,
|
|
699
|
+
patternId: response.pattern_id,
|
|
700
|
+
correlation: response.metrics?.success_correlation || response.correlation,
|
|
701
|
+
projectCount: response.metrics?.project_count,
|
|
702
|
+
developerCount: response.metrics?.developer_count,
|
|
703
|
+
sessionCount: response.metrics?.session_count,
|
|
704
|
+
category: response.proposed_category || pattern.category,
|
|
705
|
+
candidateId: response.candidate_id
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
return null;
|
|
710
|
+
} catch (error) {
|
|
711
|
+
console.error('[Rapport] evaluateForPromotion error:', error.message);
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Record standards shown during session (Phase 7)
|
|
718
|
+
* Fire-and-forget to avoid blocking session-start hook
|
|
719
|
+
* @param {string} sessionId - Session identifier
|
|
720
|
+
* @param {Array} standards - Array of standards that were injected
|
|
721
|
+
*/
|
|
722
|
+
recordStandardsShown(sessionId, standards) {
|
|
723
|
+
// Fire and forget - don't await to keep hook fast
|
|
724
|
+
this._recordStandardsAsync(sessionId, standards).catch(err => {
|
|
725
|
+
console.error('[Rapport] recordStandardsShown error:', err.message);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async _recordStandardsAsync(sessionId, standards) {
|
|
730
|
+
if (!standards || standards.length === 0) return;
|
|
731
|
+
|
|
732
|
+
const apiUrl = this.config.apiUrl;
|
|
733
|
+
const https = require('https');
|
|
734
|
+
const url = require('url');
|
|
735
|
+
|
|
736
|
+
// Prepare payload
|
|
737
|
+
const payload = JSON.stringify({
|
|
738
|
+
session_id: sessionId,
|
|
739
|
+
standards: standards.map(s => ({
|
|
740
|
+
pattern_id: s.pattern_id || s.element,
|
|
741
|
+
relevance_score: s.score || 0
|
|
742
|
+
}))
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// Non-blocking HTTP request
|
|
746
|
+
const parsedUrl = url.parse(`${apiUrl}/api/sessions/standards`);
|
|
747
|
+
|
|
748
|
+
const options = {
|
|
749
|
+
hostname: parsedUrl.hostname,
|
|
750
|
+
port: parsedUrl.port || 443,
|
|
751
|
+
path: parsedUrl.path,
|
|
752
|
+
method: 'POST',
|
|
753
|
+
headers: {
|
|
754
|
+
'Content-Type': 'application/json',
|
|
755
|
+
'Content-Length': Buffer.byteLength(payload)
|
|
756
|
+
},
|
|
757
|
+
timeout: 2000 // 2s timeout
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
return new Promise((resolve, reject) => {
|
|
761
|
+
const req = https.request(options, (res) => {
|
|
762
|
+
resolve({ statusCode: res.statusCode });
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
req.on('error', (err) => {
|
|
766
|
+
// Log but don't fail - graceful degradation
|
|
767
|
+
console.error('[Rapport] API call failed:', err.message);
|
|
768
|
+
resolve({ error: err.message });
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
req.on('timeout', () => {
|
|
772
|
+
req.destroy();
|
|
773
|
+
resolve({ error: 'timeout' });
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
req.write(payload);
|
|
777
|
+
req.end();
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Generate session ID for tracking
|
|
783
|
+
*/
|
|
784
|
+
generateSessionId() {
|
|
785
|
+
const crypto = require('crypto');
|
|
786
|
+
const timestamp = Date.now().toString(36);
|
|
787
|
+
const random = crypto.randomBytes(4).toString('hex');
|
|
788
|
+
return `ses_${timestamp}_${random}`;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// ============================================================================
|
|
792
|
+
// Helper Methods
|
|
793
|
+
// ============================================================================
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Make authenticated API request
|
|
797
|
+
* @private
|
|
798
|
+
* @param {string} url - Full URL to request
|
|
799
|
+
* @param {string} method - HTTP method (GET, POST, PUT, DELETE)
|
|
800
|
+
* @param {Object} body - Request body (for POST/PUT)
|
|
801
|
+
* @param {number} timeout - Request timeout in milliseconds
|
|
802
|
+
* @returns {Promise<Object>} Parsed JSON response or error object
|
|
803
|
+
*/
|
|
804
|
+
async _makeApiRequest(url, method = 'GET', body = null, timeout = 10000) {
|
|
805
|
+
const https = require('https');
|
|
806
|
+
const http = require('http');
|
|
807
|
+
const urlModule = require('url');
|
|
808
|
+
|
|
809
|
+
const parsedUrl = urlModule.parse(url);
|
|
810
|
+
const isHttps = parsedUrl.protocol === 'https:';
|
|
811
|
+
const httpModule = isHttps ? https : http;
|
|
812
|
+
|
|
813
|
+
const payload = body ? JSON.stringify(body) : null;
|
|
814
|
+
|
|
815
|
+
const options = {
|
|
816
|
+
hostname: parsedUrl.hostname,
|
|
817
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
818
|
+
path: parsedUrl.path,
|
|
819
|
+
method: method,
|
|
820
|
+
headers: {
|
|
821
|
+
'Content-Type': 'application/json',
|
|
822
|
+
'Accept': 'application/json'
|
|
823
|
+
},
|
|
824
|
+
timeout: timeout
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
// Add auth header if token is available
|
|
828
|
+
if (this.config.authToken) {
|
|
829
|
+
options.headers['Authorization'] = `Bearer ${this.config.authToken}`;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Add content length for POST/PUT
|
|
833
|
+
if (payload) {
|
|
834
|
+
options.headers['Content-Length'] = Buffer.byteLength(payload);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return new Promise((resolve) => {
|
|
838
|
+
const req = httpModule.request(options, (res) => {
|
|
839
|
+
let data = '';
|
|
840
|
+
|
|
841
|
+
res.on('data', (chunk) => {
|
|
842
|
+
data += chunk;
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
res.on('end', () => {
|
|
846
|
+
try {
|
|
847
|
+
const parsed = JSON.parse(data);
|
|
848
|
+
|
|
849
|
+
// Check for error status codes
|
|
850
|
+
if (res.statusCode >= 400) {
|
|
851
|
+
resolve({
|
|
852
|
+
error: parsed.message || parsed.error || `HTTP ${res.statusCode}`,
|
|
853
|
+
status: res.statusCode,
|
|
854
|
+
...parsed
|
|
855
|
+
});
|
|
856
|
+
} else {
|
|
857
|
+
resolve(parsed);
|
|
858
|
+
}
|
|
859
|
+
} catch (e) {
|
|
860
|
+
// Non-JSON response
|
|
861
|
+
if (res.statusCode >= 400) {
|
|
862
|
+
resolve({
|
|
863
|
+
error: `HTTP ${res.statusCode}: ${data}`,
|
|
864
|
+
status: res.statusCode
|
|
865
|
+
});
|
|
866
|
+
} else {
|
|
867
|
+
resolve({ data: data, status: res.statusCode });
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
req.on('error', (err) => {
|
|
874
|
+
console.error('[Rapport] API request error:', err.message);
|
|
875
|
+
resolve({ error: err.message });
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
req.on('timeout', () => {
|
|
879
|
+
req.destroy();
|
|
880
|
+
resolve({ error: 'Request timeout' });
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
if (payload) {
|
|
884
|
+
req.write(payload);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
req.end();
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Extract file changes from session transcript
|
|
893
|
+
*/
|
|
894
|
+
extractFileChanges(sessionTranscript) {
|
|
895
|
+
const files = [];
|
|
896
|
+
|
|
897
|
+
// Simple extraction - look for common patterns
|
|
898
|
+
if (sessionTranscript.transcript) {
|
|
899
|
+
const matches = sessionTranscript.transcript.match(/(?:created|modified|updated)\s+([^\s]+\.(?:js|ts|tsx|jsx|sql|yaml))/gi);
|
|
900
|
+
if (matches) {
|
|
901
|
+
matches.forEach(match => {
|
|
902
|
+
const file = match.match(/([^\s]+\.(?:js|ts|tsx|jsx|sql|yaml))/i);
|
|
903
|
+
if (file) {
|
|
904
|
+
files.push(file[1]);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return [...new Set(files)]; // Deduplicate
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Extract patterns from a file
|
|
915
|
+
*/
|
|
916
|
+
async extractPatternsFromFile(filePath) {
|
|
917
|
+
try {
|
|
918
|
+
const fs = require('fs').promises;
|
|
919
|
+
const path = require('path');
|
|
920
|
+
|
|
921
|
+
const fullPath = path.join(this.config.projectPath, filePath);
|
|
922
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
923
|
+
|
|
924
|
+
const patterns = [];
|
|
925
|
+
|
|
926
|
+
// Detect common patterns
|
|
927
|
+
if (content.includes('wrapHandler')) {
|
|
928
|
+
patterns.push({
|
|
929
|
+
element: 'Lambda Handler Pattern',
|
|
930
|
+
rule: 'Use wrapHandler for Lambda functions',
|
|
931
|
+
file: filePath
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (content.includes('executeQuery')) {
|
|
936
|
+
patterns.push({
|
|
937
|
+
element: 'Database Query Pattern',
|
|
938
|
+
rule: 'Use executeQuery helper',
|
|
939
|
+
file: filePath
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (content.includes('createSuccessResponse')) {
|
|
944
|
+
patterns.push({
|
|
945
|
+
element: 'API Response Pattern',
|
|
946
|
+
rule: 'Use createSuccessResponse helper',
|
|
947
|
+
file: filePath
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return patterns;
|
|
952
|
+
} catch (error) {
|
|
953
|
+
return [];
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Backward compatibility alias
|
|
959
|
+
const RapportClient = MindmeldClient;
|
|
960
|
+
|
|
961
|
+
module.exports = {
|
|
962
|
+
MindmeldClient,
|
|
963
|
+
RapportClient,
|
|
964
|
+
TeamLoadBearingDetector,
|
|
965
|
+
CollaborationPrompt,
|
|
966
|
+
RelevanceDetector,
|
|
967
|
+
PatternValidator,
|
|
968
|
+
StandardsIngestion
|
|
969
|
+
};
|