@equilateral_ai/mindmeld 3.1.2 → 3.3.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 +4 -4
- package/hooks/README.md +46 -4
- package/hooks/pre-compact.js +115 -12
- package/hooks/session-end.js +292 -0
- package/hooks/session-start.js +302 -25
- package/package.json +5 -2
- package/scripts/auth-login.js +53 -0
- package/scripts/harvest.js +59 -19
- package/scripts/init-project.js +134 -374
- package/scripts/inject.js +30 -9
- package/scripts/repo-analyzer.js +870 -0
- package/src/core/AuthManager.js +498 -0
- package/src/core/CrossReferenceEngine.js +624 -0
- package/src/core/DeprecationScheduler.js +183 -0
- package/src/core/LLMPatternDetector.js +218 -0
- package/src/core/RapportOrchestrator.js +186 -0
- package/src/core/RelevanceDetector.js +32 -2
- package/src/core/StandardLifecycle.js +244 -0
- package/src/core/StandardsIngestion.js +341 -28
- package/src/core/parsers/adrParser.js +479 -0
- package/src/core/parsers/cursorRulesParser.js +564 -0
- package/src/core/parsers/eslintParser.js +439 -0
- package/src/handlers/alerts/alertsAcknowledge.js +4 -3
- package/src/handlers/analytics/activitySummaryGet.js +235 -0
- package/src/handlers/analytics/coachingGet.js +361 -0
- package/src/handlers/analytics/developerScoreGet.js +207 -0
- package/src/handlers/collaborators/collaboratorAdd.js +4 -5
- package/src/handlers/collaborators/collaboratorInvite.js +6 -5
- package/src/handlers/collaborators/collaboratorList.js +3 -3
- package/src/handlers/collaborators/collaboratorRemove.js +5 -4
- package/src/handlers/correlations/correlationsDeveloperGet.js +12 -11
- package/src/handlers/correlations/correlationsGet.js +1 -1
- package/src/handlers/correlations/correlationsProjectGet.js +7 -6
- package/src/handlers/enterprise/enterpriseAuditGet.js +108 -0
- package/src/handlers/enterprise/enterpriseContributorsGet.js +85 -0
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +53 -0
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +77 -0
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +71 -0
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +87 -0
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +122 -0
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +77 -0
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +138 -0
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +89 -0
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +90 -0
- package/src/handlers/github/githubConnectionStatus.js +1 -1
- package/src/handlers/github/githubDiscoverPatterns.js +264 -5
- package/src/handlers/github/githubOAuthCallback.js +14 -2
- package/src/handlers/github/githubOAuthStart.js +1 -1
- package/src/handlers/github/githubPatternsReview.js +1 -1
- package/src/handlers/github/githubReposList.js +1 -1
- package/src/handlers/helpers/auditLogger.js +201 -0
- package/src/handlers/helpers/index.js +19 -1
- package/src/handlers/helpers/lambdaWrapper.js +1 -1
- package/src/handlers/notifications/sendNotification.js +1 -1
- package/src/handlers/projects/projectCreate.js +28 -1
- package/src/handlers/projects/projectDelete.js +3 -3
- package/src/handlers/projects/projectUpdate.js +4 -5
- package/src/handlers/scheduled/analyzeCorrelations.js +3 -3
- package/src/handlers/scheduled/generateAlerts.js +1 -1
- package/src/handlers/standards/catalogGet.js +185 -0
- package/src/handlers/standards/catalogSync.js +120 -0
- package/src/handlers/standards/projectStandardsGet.js +135 -0
- package/src/handlers/standards/projectStandardsPut.js +131 -0
- package/src/handlers/standards/standardsAuditGet.js +65 -0
- package/src/handlers/standards/standardsParseUpload.js +153 -0
- package/src/handlers/standards/standardsRelevantPost.js +213 -0
- package/src/handlers/standards/standardsTransition.js +64 -0
- package/src/handlers/user/userSplashAck.js +91 -0
- package/src/handlers/user/userSplashGet.js +194 -0
- package/src/handlers/users/userProfilePut.js +77 -0
- package/src/index.js +37 -29
package/hooks/session-start.js
CHANGED
|
@@ -30,6 +30,57 @@ function generateFingerprint(userId, companyId, tier) {
|
|
|
30
30
|
return `<!-- fp:${base64Fingerprint} -->`;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Load project standards preferences from local config
|
|
35
|
+
* @returns {Promise<Object|null>} Preferences or null if not set
|
|
36
|
+
*/
|
|
37
|
+
async function loadStandardsPreferences() {
|
|
38
|
+
try {
|
|
39
|
+
const prefsPath = path.join(process.cwd(), '.mindmeld', 'preferences.json');
|
|
40
|
+
const prefsContent = await fs.readFile(prefsPath, 'utf-8');
|
|
41
|
+
return JSON.parse(prefsContent);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Expected: preferences don't exist yet
|
|
44
|
+
if (error.code !== 'ENOENT' && !(error instanceof SyntaxError)) {
|
|
45
|
+
console.error('Unexpected error loading standards preferences:', error.message);
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Filter standards by project preferences
|
|
53
|
+
* @param {Array} standards - All relevant standards
|
|
54
|
+
* @param {Object} preferences - Project preferences
|
|
55
|
+
* @returns {Array} Filtered standards
|
|
56
|
+
*/
|
|
57
|
+
function filterStandardsByPreferences(standards, preferences) {
|
|
58
|
+
if (!preferences) {
|
|
59
|
+
return standards; // No preferences = all standards
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const enabledCategories = preferences.enabled_categories || {};
|
|
63
|
+
const standardOverrides = preferences.standard_overrides || {};
|
|
64
|
+
|
|
65
|
+
return standards.filter(standard => {
|
|
66
|
+
const category = standard.category;
|
|
67
|
+
const standardPath = standard.path || `${category}/${standard.element}.md`;
|
|
68
|
+
|
|
69
|
+
// Check individual override first (highest priority)
|
|
70
|
+
if (standardPath in standardOverrides) {
|
|
71
|
+
return standardOverrides[standardPath] === true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check category-level setting
|
|
75
|
+
if (category in enabledCategories) {
|
|
76
|
+
return enabledCategories[category] === true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Default: include if no preference set
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
33
84
|
/**
|
|
34
85
|
* Load user and company info from MindMeld config
|
|
35
86
|
* @returns {Promise<{userId: string, companyId: string, tier: string}>}
|
|
@@ -45,7 +96,11 @@ async function loadFingerprintConfig() {
|
|
|
45
96
|
companyId: config.company_id || process.env.MINDMELD_COMPANY_ID || 'unknown',
|
|
46
97
|
tier: config.subscription_tier || process.env.MINDMELD_TIER || 'free'
|
|
47
98
|
};
|
|
48
|
-
} catch {
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// Expected: config doesn't exist or invalid JSON
|
|
101
|
+
if (error.code !== 'ENOENT' && !(error instanceof SyntaxError)) {
|
|
102
|
+
console.error('Unexpected error loading fingerprint config:', error.message);
|
|
103
|
+
}
|
|
49
104
|
// Fallback to environment variables
|
|
50
105
|
return {
|
|
51
106
|
userId: process.env.MINDMELD_USER_ID || 'anonymous',
|
|
@@ -55,6 +110,183 @@ async function loadFingerprintConfig() {
|
|
|
55
110
|
}
|
|
56
111
|
}
|
|
57
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Load auth token for API calls
|
|
115
|
+
* Priority: env var → project credentials.json → global ~/.mindmeld/auth.json
|
|
116
|
+
* If no token found, spawns background browser login for next session.
|
|
117
|
+
* @returns {Promise<string|null>} Auth token or null
|
|
118
|
+
*/
|
|
119
|
+
async function loadAuthToken() {
|
|
120
|
+
// 1. Env var (highest priority)
|
|
121
|
+
if (process.env.MINDMELD_AUTH_TOKEN) {
|
|
122
|
+
return process.env.MINDMELD_AUTH_TOKEN;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 2. Project-level credentials.json
|
|
126
|
+
try {
|
|
127
|
+
const credPath = path.join(process.cwd(), '.mindmeld', 'credentials.json');
|
|
128
|
+
const content = await fs.readFile(credPath, 'utf-8');
|
|
129
|
+
const creds = JSON.parse(content);
|
|
130
|
+
if (creds.auth_token || creds.token) {
|
|
131
|
+
return creds.auth_token || creds.token;
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
// No project-level credentials
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 3. Global ~/.mindmeld/auth.json (from browser login / mindmeld CLI)
|
|
138
|
+
try {
|
|
139
|
+
const { AuthManager } = require('../src/core/AuthManager');
|
|
140
|
+
const cognitoConfig = await loadCognitoConfig();
|
|
141
|
+
const authManager = new AuthManager(cognitoConfig);
|
|
142
|
+
const token = await authManager.getValidToken();
|
|
143
|
+
if (token) {
|
|
144
|
+
return token;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 4. No valid token — spawn background browser login
|
|
148
|
+
spawnBackgroundLogin();
|
|
149
|
+
return null;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Load Cognito config from .myworld.json (dev vs prod detection)
|
|
157
|
+
* @returns {Promise<Object>} Cognito constructor options or empty object (uses prod defaults)
|
|
158
|
+
*/
|
|
159
|
+
async function loadCognitoConfig() {
|
|
160
|
+
try {
|
|
161
|
+
const configPath = path.join(process.cwd(), '.myworld.json');
|
|
162
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
163
|
+
const config = JSON.parse(content);
|
|
164
|
+
const auth = config.deployments?.backend?.auth;
|
|
165
|
+
if (auth?.domain && auth?.client_id) {
|
|
166
|
+
return {
|
|
167
|
+
cognitoDomain: `${auth.domain}.auth.us-east-2.amazoncognito.com`,
|
|
168
|
+
cognitoClientId: auth.client_id
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
// No .myworld.json — use production defaults
|
|
173
|
+
}
|
|
174
|
+
return {};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Spawn a detached background process to open the browser for Cognito login.
|
|
179
|
+
* The current session continues with file-based fallback.
|
|
180
|
+
* Next session will pick up the saved token from ~/.mindmeld/auth.json.
|
|
181
|
+
*/
|
|
182
|
+
function spawnBackgroundLogin() {
|
|
183
|
+
try {
|
|
184
|
+
const { spawn } = require('child_process');
|
|
185
|
+
const loginScript = path.resolve(__dirname, '../scripts/auth-login.js');
|
|
186
|
+
// Map child's stdout → parent's stderr so it doesn't mix with hook's context output
|
|
187
|
+
const child = spawn(process.execPath, [loginScript], {
|
|
188
|
+
detached: true,
|
|
189
|
+
stdio: ['ignore', 2, 2],
|
|
190
|
+
cwd: process.cwd()
|
|
191
|
+
});
|
|
192
|
+
child.unref();
|
|
193
|
+
console.error('[MindMeld] No auth token. Opening browser for login...');
|
|
194
|
+
console.error('[MindMeld] Complete login in your browser. API standards available next session.');
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error('[MindMeld] Run "mindmeld login" to authenticate for API-backed standards.');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Load API and Cognito configuration from .myworld.json
|
|
202
|
+
* @returns {Promise<{apiUrl: string, cognitoDomain?: string, cognitoClientId?: string}>}
|
|
203
|
+
*/
|
|
204
|
+
async function loadApiConfig() {
|
|
205
|
+
try {
|
|
206
|
+
const configPath = path.join(process.cwd(), '.myworld.json');
|
|
207
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
208
|
+
const config = JSON.parse(content);
|
|
209
|
+
const backend = config.deployments?.backend;
|
|
210
|
+
const auth = backend?.auth;
|
|
211
|
+
return {
|
|
212
|
+
apiUrl: backend?.api?.base_url || 'https://api.mindmeld.dev',
|
|
213
|
+
cognitoDomain: auth?.domain ? `${auth.domain}.auth.us-east-2.amazoncognito.com` : undefined,
|
|
214
|
+
cognitoClientId: auth?.client_id || undefined
|
|
215
|
+
};
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return {
|
|
218
|
+
apiUrl: process.env.MINDMELD_API_URL || 'https://api.mindmeld.dev'
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Fetch relevant standards from the Cognito-protected API
|
|
225
|
+
* @param {string} apiUrl - API base URL
|
|
226
|
+
* @param {string} authToken - Cognito JWT
|
|
227
|
+
* @param {Object} characteristics - Detected project characteristics
|
|
228
|
+
* @param {string} projectId - Project ID
|
|
229
|
+
* @param {Object} preferences - User preferences
|
|
230
|
+
* @returns {Promise<Array>} Relevant standards
|
|
231
|
+
*/
|
|
232
|
+
async function fetchRelevantStandardsFromAPI(apiUrl, authToken, characteristics, projectId, preferences) {
|
|
233
|
+
if (!authToken) {
|
|
234
|
+
throw new Error('No auth token available');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const https = require('https');
|
|
238
|
+
const url = require('url');
|
|
239
|
+
|
|
240
|
+
const payload = JSON.stringify({
|
|
241
|
+
characteristics,
|
|
242
|
+
projectId: projectId || undefined,
|
|
243
|
+
preferences: preferences || undefined
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const parsedUrl = url.parse(`${apiUrl}/api/standards/relevant`);
|
|
247
|
+
|
|
248
|
+
const options = {
|
|
249
|
+
hostname: parsedUrl.hostname,
|
|
250
|
+
port: parsedUrl.port || 443,
|
|
251
|
+
path: parsedUrl.path,
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: {
|
|
254
|
+
'Content-Type': 'application/json',
|
|
255
|
+
'Authorization': `Bearer ${authToken}`,
|
|
256
|
+
'Content-Length': Buffer.byteLength(payload)
|
|
257
|
+
},
|
|
258
|
+
timeout: 3000
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
return new Promise((resolve, reject) => {
|
|
262
|
+
const req = https.request(options, (res) => {
|
|
263
|
+
let data = '';
|
|
264
|
+
res.on('data', chunk => { data += chunk; });
|
|
265
|
+
res.on('end', () => {
|
|
266
|
+
try {
|
|
267
|
+
const parsed = JSON.parse(data);
|
|
268
|
+
if (res.statusCode >= 400) {
|
|
269
|
+
reject(new Error(parsed.message || `HTTP ${res.statusCode}`));
|
|
270
|
+
} else {
|
|
271
|
+
resolve(parsed.standards || []);
|
|
272
|
+
}
|
|
273
|
+
} catch (e) {
|
|
274
|
+
reject(new Error('Invalid API response'));
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
req.on('error', reject);
|
|
280
|
+
req.on('timeout', () => {
|
|
281
|
+
req.destroy();
|
|
282
|
+
reject(new Error('API request timeout'));
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
req.write(payload);
|
|
286
|
+
req.end();
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
58
290
|
/**
|
|
59
291
|
* Main hook execution
|
|
60
292
|
*/
|
|
@@ -65,54 +297,95 @@ async function injectContext() {
|
|
|
65
297
|
// Fast bail if MindMeld not configured
|
|
66
298
|
const hasMindmeld = await checkMindmeldConfiguration();
|
|
67
299
|
if (!hasMindmeld) {
|
|
300
|
+
// Check if user is a subscriber but this project isn't initialized
|
|
301
|
+
try {
|
|
302
|
+
const os = require('os');
|
|
303
|
+
const authPath = path.join(os.homedir(), '.mindmeld', 'auth.json');
|
|
304
|
+
await fs.access(authPath);
|
|
305
|
+
console.error('[MindMeld] Project not initialized. Run "mindmeld init" in this directory.');
|
|
306
|
+
} catch (e) {
|
|
307
|
+
// No auth file — not a subscriber, stay silent
|
|
308
|
+
}
|
|
68
309
|
return '';
|
|
69
310
|
}
|
|
70
311
|
|
|
71
|
-
//
|
|
72
|
-
const fingerprintConfig = await
|
|
312
|
+
// 1. Parallel local reads (no network, no DB)
|
|
313
|
+
const [fingerprintConfig, authToken, apiConfig, preferences] = await Promise.all([
|
|
314
|
+
loadFingerprintConfig(),
|
|
315
|
+
loadAuthToken(),
|
|
316
|
+
loadApiConfig(),
|
|
317
|
+
loadStandardsPreferences()
|
|
318
|
+
]);
|
|
73
319
|
|
|
74
|
-
//
|
|
320
|
+
// 2. Initialize MindmeldClient (used for project detection, session tracking, team context)
|
|
75
321
|
const { MindmeldClient } = require('../src/index');
|
|
76
|
-
|
|
77
322
|
const mindmeld = new MindmeldClient({
|
|
78
323
|
projectPath: process.cwd(),
|
|
79
|
-
standardsPath: path.join(process.cwd(), '.equilateral-standards')
|
|
324
|
+
standardsPath: path.join(process.cwd(), '.equilateral-standards'),
|
|
325
|
+
authToken: authToken,
|
|
326
|
+
apiUrl: apiConfig.apiUrl
|
|
80
327
|
});
|
|
81
328
|
|
|
82
|
-
//
|
|
83
|
-
await mindmeld.ensureStandardsIngested();
|
|
84
|
-
|
|
85
|
-
// 2. Detect project context
|
|
329
|
+
// 3. Detect project from local config
|
|
86
330
|
const context = await mindmeld.detectProject();
|
|
87
|
-
|
|
88
331
|
if (!context) {
|
|
89
332
|
return ''; // No MindMeld project
|
|
90
333
|
}
|
|
91
334
|
|
|
92
|
-
// 3. Generate session ID for tracking
|
|
93
335
|
const sessionId = mindmeld.generateSessionId();
|
|
94
336
|
|
|
95
|
-
// 4.
|
|
96
|
-
const
|
|
97
|
-
|
|
337
|
+
// 4. Detect project characteristics locally (file system only, no DB)
|
|
338
|
+
const characteristics = await mindmeld.relevanceDetector.detectProjectCharacteristics();
|
|
339
|
+
|
|
340
|
+
// 5. Parallel API calls: standards + team context
|
|
341
|
+
const [standardsResult, projectContextResult] = await Promise.allSettled([
|
|
342
|
+
fetchRelevantStandardsFromAPI(apiConfig.apiUrl, authToken, characteristics, context.projectId, preferences),
|
|
343
|
+
mindmeld.loadProjectContext(context.projectId)
|
|
344
|
+
]);
|
|
345
|
+
|
|
346
|
+
// 6. Resolve standards: API → file-based fallback
|
|
347
|
+
let relevantStandards = [];
|
|
348
|
+
if (standardsResult.status === 'fulfilled' && standardsResult.value.length > 0) {
|
|
349
|
+
relevantStandards = standardsResult.value;
|
|
350
|
+
console.error(`[MindMeld] ${relevantStandards.length} standards from API`);
|
|
351
|
+
} else {
|
|
352
|
+
if (standardsResult.status === 'rejected') {
|
|
353
|
+
console.error(`[MindMeld] API fallback: ${standardsResult.reason.message}`);
|
|
354
|
+
}
|
|
355
|
+
const categories = mindmeld.relevanceDetector.mapCharacteristicsToCategories(characteristics);
|
|
356
|
+
relevantStandards = await mindmeld.relevanceDetector.loadStandardsFromFiles(categories);
|
|
357
|
+
console.error(`[MindMeld] ${relevantStandards.length} standards from file fallback`);
|
|
358
|
+
}
|
|
98
359
|
|
|
99
|
-
//
|
|
100
|
-
|
|
360
|
+
// 7. Filter by project preferences
|
|
361
|
+
if (preferences) {
|
|
362
|
+
relevantStandards = filterStandardsByPreferences(relevantStandards, preferences);
|
|
363
|
+
console.error(`[MindMeld] Filtered to ${relevantStandards.length} standards by preferences`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Take top 10 most relevant after filtering
|
|
367
|
+
relevantStandards = relevantStandards.slice(0, 10);
|
|
101
368
|
|
|
102
|
-
//
|
|
103
|
-
|
|
369
|
+
// 8. Record standards shown (fire-and-forget, non-blocking)
|
|
370
|
+
mindmeld.recordStandardsShown(sessionId, relevantStandards);
|
|
104
371
|
|
|
105
|
-
//
|
|
106
|
-
const
|
|
372
|
+
// 9. Resolve team context from parallel API call
|
|
373
|
+
const projectContext = projectContextResult.status === 'fulfilled' ? projectContextResult.value : null;
|
|
374
|
+
const teamPatterns = projectContext && projectContext.patterns
|
|
375
|
+
? projectContext.patterns.filter(p => p.confidence > 0.7)
|
|
376
|
+
: [];
|
|
377
|
+
const recentLearning = projectContext && projectContext.recentLearning
|
|
378
|
+
? projectContext.recentLearning
|
|
379
|
+
: [];
|
|
107
380
|
|
|
108
|
-
//
|
|
381
|
+
// 10. Build context injection with fingerprint
|
|
109
382
|
const injection = formatContextInjection({
|
|
110
383
|
project: context.projectName,
|
|
111
384
|
sessionId: sessionId,
|
|
112
385
|
collaborators: context.collaborators,
|
|
113
386
|
relevantStandards: relevantStandards,
|
|
114
|
-
teamPatterns: teamPatterns
|
|
115
|
-
recentLearning: recentLearning
|
|
387
|
+
teamPatterns: teamPatterns,
|
|
388
|
+
recentLearning: recentLearning,
|
|
116
389
|
fingerprint: fingerprintConfig
|
|
117
390
|
});
|
|
118
391
|
|
|
@@ -136,7 +409,11 @@ async function checkMindmeldConfiguration() {
|
|
|
136
409
|
const mindmeldConfig = path.join(process.cwd(), '.mindmeld', 'config.json');
|
|
137
410
|
await fs.access(mindmeldConfig);
|
|
138
411
|
return true;
|
|
139
|
-
} catch {
|
|
412
|
+
} catch (error) {
|
|
413
|
+
// Expected: config doesn't exist
|
|
414
|
+
if (error.code !== 'ENOENT') {
|
|
415
|
+
console.error('Unexpected error checking MindMeld config:', error.message);
|
|
416
|
+
}
|
|
140
417
|
return false;
|
|
141
418
|
}
|
|
142
419
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equilateral_ai/mindmeld",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Intelligent standards injection for AI coding sessions - context-aware, self-documenting, scales to large codebases",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
"src/",
|
|
11
11
|
"hooks/",
|
|
12
12
|
"scripts/init-project.js",
|
|
13
|
+
"scripts/auth-login.js",
|
|
13
14
|
"scripts/inject.js",
|
|
14
15
|
"scripts/harvest.js",
|
|
16
|
+
"scripts/repo-analyzer.js",
|
|
15
17
|
"README.md"
|
|
16
18
|
],
|
|
17
19
|
"publishConfig": {
|
|
@@ -46,7 +48,8 @@
|
|
|
46
48
|
"claudeCode": {
|
|
47
49
|
"hooks": {
|
|
48
50
|
"sessionStart": "hooks/session-start.js",
|
|
49
|
-
"preCompact": "hooks/pre-compact.js"
|
|
51
|
+
"preCompact": "hooks/pre-compact.js",
|
|
52
|
+
"sessionEnd": "hooks/session-end.js"
|
|
50
53
|
},
|
|
51
54
|
"config": {
|
|
52
55
|
"standardsPath": ".equilateral-standards",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MindMeld Auth Login - Standalone browser authentication
|
|
4
|
+
*
|
|
5
|
+
* Spawned by the session-start hook as a detached background process
|
|
6
|
+
* when no auth token is found. Opens browser for Cognito PKCE login,
|
|
7
|
+
* waits for callback, saves tokens to ~/.mindmeld/auth.json.
|
|
8
|
+
*
|
|
9
|
+
* Reads .myworld.json from CWD to detect dev vs prod Cognito pool.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node scripts/auth-login.js
|
|
13
|
+
*
|
|
14
|
+
* @equilateral_ai/mindmeld
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const { AuthManager } = require('../src/core/AuthManager');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load Cognito config from .myworld.json if present
|
|
23
|
+
*/
|
|
24
|
+
function loadCognitoConfig() {
|
|
25
|
+
try {
|
|
26
|
+
const configPath = path.join(process.cwd(), '.myworld.json');
|
|
27
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
28
|
+
const config = JSON.parse(content);
|
|
29
|
+
const auth = config.deployments?.backend?.auth;
|
|
30
|
+
if (auth?.domain && auth?.client_id) {
|
|
31
|
+
return {
|
|
32
|
+
cognitoDomain: `${auth.domain}.auth.us-east-2.amazoncognito.com`,
|
|
33
|
+
cognitoClientId: auth.client_id
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// No .myworld.json — use production defaults
|
|
38
|
+
}
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const cognitoConfig = loadCognitoConfig();
|
|
43
|
+
const authManager = new AuthManager(cognitoConfig);
|
|
44
|
+
|
|
45
|
+
authManager.browserAuth()
|
|
46
|
+
.then(tokens => {
|
|
47
|
+
console.log(`Logged in as ${tokens.email}`);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
})
|
|
50
|
+
.catch(err => {
|
|
51
|
+
console.error('Login failed:', err.message);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
});
|
package/scripts/harvest.js
CHANGED
|
@@ -71,13 +71,27 @@ async function getGitHistory(projectPath, options = {}) {
|
|
|
71
71
|
try {
|
|
72
72
|
const { stdout: log } = await execAsync(
|
|
73
73
|
`git log --since="${since}" -n ${maxCommits} --format="%H|%s|%an|%ai" --stat`,
|
|
74
|
-
{ cwd: projectPath, maxBuffer: 1024 * 1024 }
|
|
74
|
+
{ cwd: projectPath, maxBuffer: 10 * 1024 * 1024 }
|
|
75
75
|
);
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
// For large repos, limit diff output to avoid buffer overflow
|
|
78
|
+
let diff = '';
|
|
79
|
+
try {
|
|
80
|
+
const { stdout } = await execAsync(
|
|
81
|
+
`git log --since="${since}" -n ${maxCommits} -p --diff-filter=M -- '*.js' '*.ts' '*.py' '*.java' '*.go' '*.rs'`,
|
|
82
|
+
{ cwd: projectPath, maxBuffer: 20 * 1024 * 1024 }
|
|
83
|
+
);
|
|
84
|
+
diff = stdout;
|
|
85
|
+
} catch (diffError) {
|
|
86
|
+
// If diff is too large, try with fewer commits
|
|
87
|
+
if (diffError.message.includes('maxBuffer')) {
|
|
88
|
+
const { stdout } = await execAsync(
|
|
89
|
+
`git log --since="${since}" -n 10 -p --diff-filter=M -- '*.js' '*.ts'`,
|
|
90
|
+
{ cwd: projectPath, maxBuffer: 10 * 1024 * 1024 }
|
|
91
|
+
);
|
|
92
|
+
diff = stdout;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
81
95
|
|
|
82
96
|
return { log, diff };
|
|
83
97
|
} catch (error) {
|
|
@@ -268,8 +282,11 @@ async function harvest(options = {}) {
|
|
|
268
282
|
console.error('\n[MindMeld] LLM-powered detection available for deeper analysis.');
|
|
269
283
|
console.error('[MindMeld] Set MINDMELD_USE_LLM=true for semantic pattern detection.');
|
|
270
284
|
}
|
|
271
|
-
} catch {
|
|
272
|
-
// LLM module not available
|
|
285
|
+
} catch (error) {
|
|
286
|
+
// Expected: LLM module not available
|
|
287
|
+
if (error.code !== 'MODULE_NOT_FOUND') {
|
|
288
|
+
console.error('Unexpected error loading LLM module:', error.message);
|
|
289
|
+
}
|
|
273
290
|
}
|
|
274
291
|
}
|
|
275
292
|
|
|
@@ -304,8 +321,11 @@ async function promotePatterns(projectPath, patterns, options = {}) {
|
|
|
304
321
|
try {
|
|
305
322
|
await fs.access(filePath);
|
|
306
323
|
continue;
|
|
307
|
-
} catch {
|
|
308
|
-
//
|
|
324
|
+
} catch (error) {
|
|
325
|
+
// Expected: file doesn't exist, safe to create
|
|
326
|
+
if (error.code !== 'ENOENT') {
|
|
327
|
+
console.error(`Unexpected error checking ${filePath}:`, error.message);
|
|
328
|
+
}
|
|
309
329
|
}
|
|
310
330
|
|
|
311
331
|
const content = [
|
|
@@ -358,7 +378,11 @@ async function harvestPlans(projectPath) {
|
|
|
358
378
|
const indexContent = await fs.readFile(sessionIndexPath, 'utf-8');
|
|
359
379
|
const index = JSON.parse(indexContent);
|
|
360
380
|
sessions = index.entries || [];
|
|
361
|
-
} catch {
|
|
381
|
+
} catch (error) {
|
|
382
|
+
// Expected: no sessions file
|
|
383
|
+
if (error.code !== 'ENOENT' && !(error instanceof SyntaxError)) {
|
|
384
|
+
console.error('Unexpected error reading sessions:', error.message);
|
|
385
|
+
}
|
|
362
386
|
return []; // No sessions found for this project
|
|
363
387
|
}
|
|
364
388
|
|
|
@@ -383,7 +407,11 @@ async function harvestPlans(projectPath) {
|
|
|
383
407
|
planFiles.push({ path: filePath, name: entry, mtime });
|
|
384
408
|
}
|
|
385
409
|
}
|
|
386
|
-
} catch {
|
|
410
|
+
} catch (error) {
|
|
411
|
+
// Expected: no plans directory
|
|
412
|
+
if (error.code !== 'ENOENT') {
|
|
413
|
+
console.error('Unexpected error reading plans:', error.message);
|
|
414
|
+
}
|
|
387
415
|
return []; // No plans directory
|
|
388
416
|
}
|
|
389
417
|
|
|
@@ -409,8 +437,11 @@ async function harvestPlans(projectPath) {
|
|
|
409
437
|
try {
|
|
410
438
|
await fs.access(path.join(projectPath, cleanRef));
|
|
411
439
|
relevance += 3; // File exists in this project
|
|
412
|
-
} catch {
|
|
413
|
-
//
|
|
440
|
+
} catch (error) {
|
|
441
|
+
// Expected: file doesn't exist here
|
|
442
|
+
if (error.code !== 'ENOENT') {
|
|
443
|
+
console.error(`Unexpected error checking ${cleanRef}:`, error.message);
|
|
444
|
+
}
|
|
414
445
|
}
|
|
415
446
|
}
|
|
416
447
|
|
|
@@ -422,8 +453,11 @@ async function harvestPlans(projectPath) {
|
|
|
422
453
|
planFile.content = content;
|
|
423
454
|
relevantPlans.push(planFile);
|
|
424
455
|
}
|
|
425
|
-
} catch {
|
|
426
|
-
//
|
|
456
|
+
} catch (error) {
|
|
457
|
+
// Expected: file not readable
|
|
458
|
+
if (error.code !== 'ENOENT' && error.code !== 'EACCES') {
|
|
459
|
+
console.error(`Unexpected error reading plan ${planFile.path}:`, error.message);
|
|
460
|
+
}
|
|
427
461
|
}
|
|
428
462
|
}
|
|
429
463
|
|
|
@@ -484,8 +518,11 @@ async function harvestPlans(projectPath) {
|
|
|
484
518
|
});
|
|
485
519
|
}
|
|
486
520
|
}
|
|
487
|
-
} catch {
|
|
488
|
-
//
|
|
521
|
+
} catch (error) {
|
|
522
|
+
// Expected: file not readable
|
|
523
|
+
if (error.code !== 'ENOENT' && error.code !== 'EACCES') {
|
|
524
|
+
console.error(`Unexpected error processing plan:`, error.message);
|
|
525
|
+
}
|
|
489
526
|
}
|
|
490
527
|
}
|
|
491
528
|
|
|
@@ -526,8 +563,11 @@ async function promoteDecisions(projectPath, decisions, options = {}) {
|
|
|
526
563
|
try {
|
|
527
564
|
await fs.access(filePath);
|
|
528
565
|
continue;
|
|
529
|
-
} catch {
|
|
530
|
-
//
|
|
566
|
+
} catch (error) {
|
|
567
|
+
// Expected: file doesn't exist, safe to create
|
|
568
|
+
if (error.code !== 'ENOENT') {
|
|
569
|
+
console.error(`Unexpected error checking ${filePath}:`, error.message);
|
|
570
|
+
}
|
|
531
571
|
}
|
|
532
572
|
|
|
533
573
|
const content = [
|