@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.
Files changed (71) hide show
  1. package/README.md +4 -4
  2. package/hooks/README.md +46 -4
  3. package/hooks/pre-compact.js +115 -12
  4. package/hooks/session-end.js +292 -0
  5. package/hooks/session-start.js +302 -25
  6. package/package.json +5 -2
  7. package/scripts/auth-login.js +53 -0
  8. package/scripts/harvest.js +59 -19
  9. package/scripts/init-project.js +134 -374
  10. package/scripts/inject.js +30 -9
  11. package/scripts/repo-analyzer.js +870 -0
  12. package/src/core/AuthManager.js +498 -0
  13. package/src/core/CrossReferenceEngine.js +624 -0
  14. package/src/core/DeprecationScheduler.js +183 -0
  15. package/src/core/LLMPatternDetector.js +218 -0
  16. package/src/core/RapportOrchestrator.js +186 -0
  17. package/src/core/RelevanceDetector.js +32 -2
  18. package/src/core/StandardLifecycle.js +244 -0
  19. package/src/core/StandardsIngestion.js +341 -28
  20. package/src/core/parsers/adrParser.js +479 -0
  21. package/src/core/parsers/cursorRulesParser.js +564 -0
  22. package/src/core/parsers/eslintParser.js +439 -0
  23. package/src/handlers/alerts/alertsAcknowledge.js +4 -3
  24. package/src/handlers/analytics/activitySummaryGet.js +235 -0
  25. package/src/handlers/analytics/coachingGet.js +361 -0
  26. package/src/handlers/analytics/developerScoreGet.js +207 -0
  27. package/src/handlers/collaborators/collaboratorAdd.js +4 -5
  28. package/src/handlers/collaborators/collaboratorInvite.js +6 -5
  29. package/src/handlers/collaborators/collaboratorList.js +3 -3
  30. package/src/handlers/collaborators/collaboratorRemove.js +5 -4
  31. package/src/handlers/correlations/correlationsDeveloperGet.js +12 -11
  32. package/src/handlers/correlations/correlationsGet.js +1 -1
  33. package/src/handlers/correlations/correlationsProjectGet.js +7 -6
  34. package/src/handlers/enterprise/enterpriseAuditGet.js +108 -0
  35. package/src/handlers/enterprise/enterpriseContributorsGet.js +85 -0
  36. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +53 -0
  37. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +77 -0
  38. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +71 -0
  39. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +87 -0
  40. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +122 -0
  41. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +77 -0
  42. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +138 -0
  43. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +89 -0
  44. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +90 -0
  45. package/src/handlers/github/githubConnectionStatus.js +1 -1
  46. package/src/handlers/github/githubDiscoverPatterns.js +264 -5
  47. package/src/handlers/github/githubOAuthCallback.js +14 -2
  48. package/src/handlers/github/githubOAuthStart.js +1 -1
  49. package/src/handlers/github/githubPatternsReview.js +1 -1
  50. package/src/handlers/github/githubReposList.js +1 -1
  51. package/src/handlers/helpers/auditLogger.js +201 -0
  52. package/src/handlers/helpers/index.js +19 -1
  53. package/src/handlers/helpers/lambdaWrapper.js +1 -1
  54. package/src/handlers/notifications/sendNotification.js +1 -1
  55. package/src/handlers/projects/projectCreate.js +28 -1
  56. package/src/handlers/projects/projectDelete.js +3 -3
  57. package/src/handlers/projects/projectUpdate.js +4 -5
  58. package/src/handlers/scheduled/analyzeCorrelations.js +3 -3
  59. package/src/handlers/scheduled/generateAlerts.js +1 -1
  60. package/src/handlers/standards/catalogGet.js +185 -0
  61. package/src/handlers/standards/catalogSync.js +120 -0
  62. package/src/handlers/standards/projectStandardsGet.js +135 -0
  63. package/src/handlers/standards/projectStandardsPut.js +131 -0
  64. package/src/handlers/standards/standardsAuditGet.js +65 -0
  65. package/src/handlers/standards/standardsParseUpload.js +153 -0
  66. package/src/handlers/standards/standardsRelevantPost.js +213 -0
  67. package/src/handlers/standards/standardsTransition.js +64 -0
  68. package/src/handlers/user/userSplashAck.js +91 -0
  69. package/src/handlers/user/userSplashGet.js +194 -0
  70. package/src/handlers/users/userProfilePut.js +77 -0
  71. package/src/index.js +37 -29
@@ -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
- // Load fingerprint config
72
- const fingerprintConfig = await loadFingerprintConfig();
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
- // Load MindmeldClient with graceful degradation
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
- // 1. Ensure standards are ingested (cached, fast check)
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. Identify relevant standards (NOT all 80+)
96
- const relevance = await mindmeld.getRelevantStandards(context);
97
- const relevantStandards = relevance.standards.slice(0, 10); // Top 10 most relevant
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
- // 5. Record standards shown (Phase 7 - fire and forget, non-blocking)
100
- mindmeld.recordStandardsShown(sessionId, relevantStandards);
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
- // 6. Load team patterns
103
- const teamPatterns = await mindmeld.loadProjectContext(context.projectId);
369
+ // 8. Record standards shown (fire-and-forget, non-blocking)
370
+ mindmeld.recordStandardsShown(sessionId, relevantStandards);
104
371
 
105
- // 7. Get recent learning (last 7 days)
106
- const recentLearning = await mindmeld.getRecentLearning(context.projectId, 7);
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
- // 8. Build context injection with fingerprint
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 ? teamPatterns.filter(p => p.correlation > 0.7) : [],
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.1.2",
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
+ });
@@ -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
- const { stdout: diff } = await execAsync(
78
- `git log --since="${since}" -n ${maxCommits} -p --diff-filter=M`,
79
- { cwd: projectPath, maxBuffer: 5 * 1024 * 1024 }
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
- // File doesn't exist, safe to create
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
- // File doesn't exist here
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
- // Skip unreadable
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
- // Skip unreadable plans
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
- // Safe to create
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 = [