@equilateral_ai/mindmeld 3.5.3 → 4.0.1

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 (139) hide show
  1. package/hooks/session-start.js +312 -85
  2. package/package.json +20 -14
  3. package/scripts/init-project.js +9 -23
  4. package/src/client/dbShim.js +16 -0
  5. package/src/core/AuthManager.js +3 -2
  6. package/src/handlers/helpers/dbOperations.js +9 -46
  7. package/src/index.js +2 -217
  8. package/src/utils/piiMask.js +16 -0
  9. package/scripts/harvest.js +0 -601
  10. package/scripts/inject.js +0 -409
  11. package/scripts/mcp-bridge.js +0 -220
  12. package/scripts/repo-analyzer.js +0 -870
  13. package/scripts/standards.js +0 -285
  14. package/src/collaboration/CollaborationPrompt.js +0 -460
  15. package/src/core/AlertEngine.js +0 -813
  16. package/src/core/AlertNotifier.js +0 -363
  17. package/src/core/CorrelationAnalyzer.js +0 -931
  18. package/src/core/CrossReferenceEngine.js +0 -624
  19. package/src/core/CurationEngine.js +0 -688
  20. package/src/core/DeprecationScheduler.js +0 -183
  21. package/src/core/LoadBearingDetector.js +0 -242
  22. package/src/core/NotificationService.js +0 -1032
  23. package/src/core/RapportOrchestrator.js +0 -632
  24. package/src/core/RelevanceDetector.js +0 -694
  25. package/src/core/StandardLifecycle.js +0 -244
  26. package/src/core/StandardsIngestion.js +0 -991
  27. package/src/core/TeamLoadBearingDetector.js +0 -431
  28. package/src/core/parsers/adrParser.js +0 -479
  29. package/src/core/parsers/cursorRulesParser.js +0 -564
  30. package/src/core/parsers/eslintParser.js +0 -439
  31. package/src/database/dbOperations.js +0 -105
  32. package/src/handlers/activity/activityGetMe.js +0 -98
  33. package/src/handlers/activity/activityGetTeam.js +0 -175
  34. package/src/handlers/admin/adminSetup.js +0 -216
  35. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  36. package/src/handlers/alerts/alertsGet.js +0 -250
  37. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  38. package/src/handlers/analytics/coachingGet.js +0 -361
  39. package/src/handlers/analytics/convergenceGet.js +0 -236
  40. package/src/handlers/analytics/developerScoreGet.js +0 -137
  41. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  42. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  43. package/src/handlers/collaborators/collaboratorList.js +0 -82
  44. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  45. package/src/handlers/collaborators/inviteAccept.js +0 -122
  46. package/src/handlers/company/companyUsersDelete.js +0 -141
  47. package/src/handlers/company/companyUsersGet.js +0 -90
  48. package/src/handlers/company/companyUsersPost.js +0 -267
  49. package/src/handlers/company/companyUsersPut.js +0 -76
  50. package/src/handlers/context/contextGet.js +0 -57
  51. package/src/handlers/context/invariantsGet.js +0 -74
  52. package/src/handlers/context/loopsGet.js +0 -82
  53. package/src/handlers/context/notesCreate.js +0 -74
  54. package/src/handlers/context/purposeGet.js +0 -78
  55. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  56. package/src/handlers/correlations/correlationsGet.js +0 -93
  57. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  58. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  59. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  60. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  61. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  62. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  63. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  64. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  65. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  66. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  67. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  68. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  69. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  70. package/src/handlers/github/githubConnectionStatus.js +0 -49
  71. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  72. package/src/handlers/github/githubOAuthCallback.js +0 -178
  73. package/src/handlers/github/githubOAuthStart.js +0 -59
  74. package/src/handlers/github/githubPatternsReview.js +0 -76
  75. package/src/handlers/github/githubReposList.js +0 -105
  76. package/src/handlers/health/healthGet.js +0 -55
  77. package/src/handlers/helpers/auditLogger.js +0 -201
  78. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  79. package/src/handlers/helpers/decisionFrames.js +0 -29
  80. package/src/handlers/helpers/errorHandler.js +0 -49
  81. package/src/handlers/helpers/index.js +0 -138
  82. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  83. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  84. package/src/handlers/helpers/predictiveCache.js +0 -51
  85. package/src/handlers/helpers/projectAccess.js +0 -88
  86. package/src/handlers/helpers/responseUtil.js +0 -55
  87. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  88. package/src/handlers/mcp/mcpHandler.js +0 -569
  89. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  90. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  91. package/src/handlers/notifications/getPreferences.js +0 -84
  92. package/src/handlers/notifications/sendNotification.js +0 -170
  93. package/src/handlers/notifications/updatePreferences.js +0 -316
  94. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  95. package/src/handlers/patterns/patternUsagePost.js +0 -182
  96. package/src/handlers/patterns/patternViolationPost.js +0 -185
  97. package/src/handlers/projects/projectCreate.js +0 -248
  98. package/src/handlers/projects/projectDelete.js +0 -82
  99. package/src/handlers/projects/projectGet.js +0 -95
  100. package/src/handlers/projects/projectUpdate.js +0 -117
  101. package/src/handlers/reports/aiLeverage.js +0 -210
  102. package/src/handlers/reports/engineeringInvestment.js +0 -132
  103. package/src/handlers/reports/riskForecast.js +0 -206
  104. package/src/handlers/reports/standardsRoi.js +0 -254
  105. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  106. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  107. package/src/handlers/scheduled/generateAlerts.js +0 -135
  108. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  109. package/src/handlers/scheduled/refreshActivity.js +0 -21
  110. package/src/handlers/scheduled/scanCompliance.js +0 -334
  111. package/src/handlers/sessions/sessionEndPost.js +0 -180
  112. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  113. package/src/handlers/standards/catalogGet.js +0 -185
  114. package/src/handlers/standards/catalogSync.js +0 -120
  115. package/src/handlers/standards/discoveriesGet.js +0 -89
  116. package/src/handlers/standards/projectStandardsGet.js +0 -129
  117. package/src/handlers/standards/projectStandardsPut.js +0 -151
  118. package/src/handlers/standards/standardsAuditGet.js +0 -65
  119. package/src/handlers/standards/standardsParseUpload.js +0 -149
  120. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  121. package/src/handlers/standards/standardsTransition.js +0 -161
  122. package/src/handlers/stripe/addonManagePost.js +0 -240
  123. package/src/handlers/stripe/billingPortalPost.js +0 -93
  124. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  125. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  126. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  127. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  128. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  129. package/src/handlers/stripe/webhookPost.js +0 -482
  130. package/src/handlers/user/apiTokenCreate.js +0 -71
  131. package/src/handlers/user/apiTokenList.js +0 -64
  132. package/src/handlers/user/userSplashAck.js +0 -91
  133. package/src/handlers/user/userSplashGet.js +0 -211
  134. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  135. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  136. package/src/handlers/users/userEntitlementsGet.js +0 -89
  137. package/src/handlers/users/userGet.js +0 -118
  138. package/src/handlers/users/userProfilePut.js +0 -77
  139. package/src/handlers/webhooks/githubWebhook.js +0 -215
@@ -1,601 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * MindMeld CLI - Manually harvest patterns from recent git history
4
- *
5
- * For non-Claude-Code tools (Cursor, Aider, Ollama, etc.) that don't have
6
- * a pre-compact hook. Analyzes recent commits to detect patterns.
7
- *
8
- * Usage:
9
- * mindmeld harvest # Analyze last 10 commits
10
- * mindmeld harvest --since 7d # Last 7 days
11
- * mindmeld harvest --commits 20 # Last 20 commits
12
- *
13
- * @equilateral_ai/mindmeld v3.0.0
14
- */
15
-
16
- const path = require('path');
17
- const fs = require('fs').promises;
18
- const { exec } = require('child_process');
19
- const { promisify } = require('util');
20
-
21
- const execAsync = promisify(exec);
22
-
23
- function showHarvestHelp() {
24
- console.log(`
25
- MindMeld harvest - Capture patterns from recent git history
26
-
27
- Usage:
28
- mindmeld harvest [options]
29
-
30
- Options:
31
- --since <period> Time period: 1d, 7d, 30d (default: 7d)
32
- --commits <n> Number of commits to analyze (default: 10)
33
- --path <dir> Project path (default: current directory)
34
- --dry-run Show what would be captured without saving
35
- --help, -h Show this help
36
-
37
- Examples:
38
- mindmeld harvest # Analyze last 7 days
39
- mindmeld harvest --since 1d # Just today's commits
40
- mindmeld harvest --commits 20 # Last 20 commits
41
- mindmeld harvest --dry-run # Preview without saving
42
-
43
- What it does:
44
- 1. Reads recent git diffs and commit messages
45
- 2. Detects repeated patterns (file types, naming, structure)
46
- 3. Identifies correction patterns (reverts, fixes, amendments)
47
- 4. Records detected patterns in .mindmeld/patterns/
48
- 5. Patterns with enough repetition get promoted to standards
49
- `);
50
- }
51
-
52
- /**
53
- * Convert shorthand period (7d, 30d, 2w) to git-compatible format
54
- */
55
- function parseGitSince(since) {
56
- const match = since.match(/^(\d+)(d|w|m)$/);
57
- if (!match) return since; // pass through if already in git format
58
- const [, num, unit] = match;
59
- const units = { d: 'days', w: 'weeks', m: 'months' };
60
- return `${num} ${units[unit] || 'days'} ago`;
61
- }
62
-
63
- /**
64
- * Get recent git activity
65
- */
66
- async function getGitHistory(projectPath, options = {}) {
67
- const since = parseGitSince(options.since || '7d');
68
- const maxCommits = options.commits || 10;
69
-
70
- // Get commit log with diffs
71
- try {
72
- const { stdout: log } = await execAsync(
73
- `git log --since="${since}" -n ${maxCommits} --format="%H|%s|%an|%ai" --stat`,
74
- { cwd: projectPath, maxBuffer: 10 * 1024 * 1024 }
75
- );
76
-
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
- }
95
-
96
- return { log, diff };
97
- } catch (error) {
98
- if (error.message.includes('not a git repository')) {
99
- throw new Error('not a git repository');
100
- }
101
- throw error;
102
- }
103
- }
104
-
105
- /**
106
- * Detect patterns from git history using regex analysis
107
- */
108
- function detectPatterns(gitHistory) {
109
- const patterns = [];
110
- const { log, diff } = gitHistory;
111
-
112
- // 1. Detect fix/correction patterns (commits that fix previous work)
113
- const fixPatterns = log.match(/fix[:\s]|correct[:\s]|revert[:\s]|wrong|mistake|should be|instead of/gi) || [];
114
- if (fixPatterns.length >= 2) {
115
- patterns.push({
116
- type: 'correction',
117
- element: 'Repeated corrections detected',
118
- evidence: `${fixPatterns.length} fix/correction commits in recent history`,
119
- confidence: Math.min(0.9, fixPatterns.length * 0.15),
120
- category: 'workflow'
121
- });
122
- }
123
-
124
- // 2. Detect file type patterns (what's being edited most)
125
- const fileExtensions = {};
126
- const fileMatches = log.match(/\S+\.\w+/g) || [];
127
- for (const file of fileMatches) {
128
- const ext = path.extname(file);
129
- if (ext && ext.length < 6) {
130
- fileExtensions[ext] = (fileExtensions[ext] || 0) + 1;
131
- }
132
- }
133
-
134
- // 3. Detect code patterns from diffs
135
- const codePatterns = [];
136
-
137
- // Connection pool vs single client
138
- if (diff.includes('createPool') || diff.includes('Pool(')) {
139
- codePatterns.push({
140
- type: 'anti-pattern',
141
- element: 'Connection pool usage detected',
142
- evidence: 'createPool or Pool() found in diffs',
143
- confidence: 0.7,
144
- category: 'database'
145
- });
146
- }
147
-
148
- // Console.log removal (cleanup pattern)
149
- const consoleAdds = (diff.match(/^\+.*console\.log/gm) || []).length;
150
- const consoleRemoves = (diff.match(/^-.*console\.log/gm) || []).length;
151
- if (consoleRemoves > consoleAdds && consoleRemoves >= 3) {
152
- codePatterns.push({
153
- type: 'pattern',
154
- element: 'Console.log cleanup pattern',
155
- evidence: `${consoleRemoves} console.log removals vs ${consoleAdds} additions`,
156
- confidence: 0.6,
157
- category: 'code-quality'
158
- });
159
- }
160
-
161
- // Error handling patterns
162
- const tryCatchAdds = (diff.match(/^\+.*try\s*\{/gm) || []).length;
163
- if (tryCatchAdds >= 3) {
164
- codePatterns.push({
165
- type: 'pattern',
166
- element: 'Error handling additions',
167
- evidence: `${tryCatchAdds} try/catch blocks added`,
168
- confidence: 0.5,
169
- category: 'error-handling'
170
- });
171
- }
172
-
173
- // Type annotation additions (TypeScript patterns)
174
- const typeAnnotations = (diff.match(/^\+.*:\s*(string|number|boolean|any|void|Promise|Array)/gm) || []).length;
175
- if (typeAnnotations >= 5) {
176
- codePatterns.push({
177
- type: 'pattern',
178
- element: 'Type annotations being added',
179
- evidence: `${typeAnnotations} type annotations in recent diffs`,
180
- confidence: 0.6,
181
- category: 'typescript'
182
- });
183
- }
184
-
185
- // Import organization patterns
186
- const importChanges = (diff.match(/^\+\s*import\s/gm) || []).length;
187
- if (importChanges >= 5) {
188
- codePatterns.push({
189
- type: 'pattern',
190
- element: 'Import reorganization',
191
- evidence: `${importChanges} import statements modified`,
192
- confidence: 0.4,
193
- category: 'code-organization'
194
- });
195
- }
196
-
197
- patterns.push(...codePatterns);
198
-
199
- return patterns;
200
- }
201
-
202
- /**
203
- * Save detected patterns to .mindmeld/patterns/
204
- */
205
- async function savePatterns(projectPath, patterns) {
206
- const patternsDir = path.join(projectPath, '.mindmeld', 'patterns');
207
- await fs.mkdir(patternsDir, { recursive: true });
208
-
209
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
210
- const harvestFile = path.join(patternsDir, `harvest-${timestamp}.json`);
211
-
212
- const harvest = {
213
- timestamp: new Date().toISOString(),
214
- source: 'manual-harvest',
215
- patterns: patterns,
216
- projectPath: projectPath,
217
- projectName: path.basename(projectPath)
218
- };
219
-
220
- await fs.writeFile(harvestFile, JSON.stringify(harvest, null, 2));
221
- return harvestFile;
222
- }
223
-
224
- /**
225
- * Main harvest execution
226
- */
227
- async function harvest(options = {}) {
228
- const projectPath = options.path || process.cwd();
229
- const dryRun = options.dryRun || false;
230
-
231
- console.error(`[MindMeld] Harvesting patterns from: ${projectPath}`);
232
- console.error(`[MindMeld] Period: last ${options.since || '7d'}, max ${options.commits || 10} commits`);
233
- console.error('');
234
-
235
- // 1. Get git history
236
- let gitHistory;
237
- try {
238
- gitHistory = await getGitHistory(projectPath, {
239
- since: options.since || '7d',
240
- commits: options.commits || 10
241
- });
242
- } catch (error) {
243
- if (error.message === 'not a git repository') {
244
- console.error('[MindMeld] Not a git repository. Harvest requires git history.');
245
- process.exit(1);
246
- }
247
- throw error;
248
- }
249
-
250
- // 2. Detect patterns
251
- const patterns = detectPatterns(gitHistory);
252
-
253
- if (patterns.length === 0) {
254
- console.error('[MindMeld] No patterns detected in recent history.');
255
- console.error('[MindMeld] Try a longer period: mindmeld harvest --since 30d');
256
- return;
257
- }
258
-
259
- // 3. Display results
260
- console.error(`[MindMeld] Detected ${patterns.length} pattern(s):\n`);
261
- for (const pattern of patterns) {
262
- const confidence = (pattern.confidence * 100).toFixed(0);
263
- console.error(` [${pattern.type}] ${pattern.element}`);
264
- console.error(` Category: ${pattern.category} | Confidence: ${confidence}%`);
265
- console.error(` Evidence: ${pattern.evidence}`);
266
- console.error('');
267
- }
268
-
269
- // 4. Save (unless dry-run)
270
- if (!dryRun) {
271
- const savedPath = await savePatterns(projectPath, patterns);
272
- console.error(`[MindMeld] Patterns saved to: ${path.relative(projectPath, savedPath)}`);
273
- console.error(`[MindMeld] Patterns with 5+ detections will be promoted to provisional standards.`);
274
- } else {
275
- console.error('[MindMeld] Dry run - no patterns saved.');
276
- }
277
-
278
- // 5. Try LLM-powered detection if available
279
- try {
280
- const llmModule = require('../src/core/LLMPatternDetector');
281
- if (llmModule && process.env.MINDMELD_USE_LLM !== 'false') {
282
- console.error('\n[MindMeld] LLM-powered detection available for deeper analysis.');
283
- console.error('[MindMeld] Set MINDMELD_USE_LLM=true for semantic pattern detection.');
284
- }
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
- }
290
- }
291
- }
292
-
293
- /**
294
- * Promote high-confidence patterns to local staging
295
- * Writes to .mindmeld/patterns/{category}/ (local, never directly to community repo)
296
- * Community promotion happens via API → PR to EquilateralAgents-Community-Standards
297
- */
298
- async function promotePatterns(projectPath, patterns, options = {}) {
299
- const threshold = options.threshold || 0.5;
300
- const standardsDir = path.join(projectPath, '.mindmeld', 'patterns');
301
-
302
- const eligible = patterns.filter(p => p.confidence >= threshold);
303
- if (eligible.length === 0) return [];
304
-
305
- await fs.mkdir(standardsDir, { recursive: true });
306
-
307
- const promoted = [];
308
-
309
- for (const pattern of eligible) {
310
- const categoryDir = path.join(standardsDir, pattern.category);
311
- await fs.mkdir(categoryDir, { recursive: true });
312
-
313
- const filename = pattern.element
314
- .toLowerCase()
315
- .replace(/[^a-z0-9]+/g, '_')
316
- .replace(/^_|_$/g, '') + '.md';
317
-
318
- const filePath = path.join(categoryDir, filename);
319
-
320
- // Don't overwrite existing standards
321
- try {
322
- await fs.access(filePath);
323
- continue;
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
- }
329
- }
330
-
331
- const content = [
332
- `# ${pattern.element}`,
333
- '',
334
- `**Status**: provisional`,
335
- `**Category**: ${pattern.category}`,
336
- `**Confidence**: ${(pattern.confidence * 100).toFixed(0)}%`,
337
- `**Source**: git-harvest`,
338
- `**Detected**: ${new Date().toISOString().split('T')[0]}`,
339
- '',
340
- '## Evidence',
341
- '',
342
- `- ${pattern.evidence}`,
343
- '',
344
- '## Guidance',
345
- '',
346
- pattern.type === 'anti-pattern'
347
- ? `- Avoid: ${pattern.element}`
348
- : `- Follow: ${pattern.element}`,
349
- '',
350
- '---',
351
- '*Provisional standard - will be promoted to solidified after 5+ consistent detections.*'
352
- ].join('\n');
353
-
354
- await fs.writeFile(filePath, content);
355
- promoted.push({ pattern, path: filePath });
356
- }
357
-
358
- return promoted;
359
- }
360
-
361
- /**
362
- * Harvest decisions from Claude Code plan files
363
- * Matches plans to project by correlating session timestamps with plan file mtimes
364
- */
365
- async function harvestPlans(projectPath) {
366
- const os = require('os');
367
- const homeDir = os.homedir();
368
- const plansDir = path.join(homeDir, '.claude', 'plans');
369
- const projectsDir = path.join(homeDir, '.claude', 'projects');
370
-
371
- // Encode project path the same way Claude Code does
372
- const encodedPath = projectPath.replace(/\//g, '-');
373
- const sessionIndexPath = path.join(projectsDir, encodedPath, 'sessions-index.json');
374
-
375
- // 1. Load sessions for this project
376
- let sessions;
377
- try {
378
- const indexContent = await fs.readFile(sessionIndexPath, 'utf-8');
379
- const index = JSON.parse(indexContent);
380
- sessions = index.entries || [];
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
- }
386
- return []; // No sessions found for this project
387
- }
388
-
389
- if (sessions.length === 0) return [];
390
-
391
- // 2. Get time window from sessions (earliest created → now, since current session may not be indexed)
392
- const sessionTimes = sessions.map(s => new Date(s.created).getTime());
393
- const windowStart = Math.min(...sessionTimes);
394
- const windowEnd = Date.now();
395
-
396
- // 3. Find plan files within the session time window
397
- let planFiles;
398
- try {
399
- const entries = await fs.readdir(plansDir);
400
- planFiles = [];
401
- for (const entry of entries) {
402
- if (!entry.endsWith('.md')) continue;
403
- const filePath = path.join(plansDir, entry);
404
- const stat = await fs.stat(filePath);
405
- const mtime = stat.mtimeMs;
406
- if (mtime >= windowStart && mtime <= windowEnd) {
407
- planFiles.push({ path: filePath, name: entry, mtime });
408
- }
409
- }
410
- } catch (error) {
411
- // Expected: no plans directory
412
- if (error.code !== 'ENOENT') {
413
- console.error('Unexpected error reading plans:', error.message);
414
- }
415
- return []; // No plans directory
416
- }
417
-
418
- // 4. Content-match: only keep plans that reference files/terms from this project
419
- const projectName = path.basename(projectPath).toLowerCase();
420
- const relevantPlans = [];
421
-
422
- for (const planFile of planFiles) {
423
- try {
424
- const content = await fs.readFile(planFile.path, 'utf-8');
425
- const contentLower = content.toLowerCase();
426
-
427
- // Score relevance by checking project indicators
428
- let relevance = 0;
429
-
430
- // Project name match (strongest signal)
431
- if (contentLower.includes(projectName)) relevance += 5;
432
-
433
- // Check if plan mentions files that exist in this project
434
- const fileRefs = content.match(/`([^`]+\.(js|ts|json|yaml|yml|py|sql))`/g) || [];
435
- for (const ref of fileRefs.slice(0, 10)) {
436
- const cleanRef = ref.replace(/`/g, '');
437
- try {
438
- await fs.access(path.join(projectPath, cleanRef));
439
- relevance += 3; // File exists in this project
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
- }
445
- }
446
- }
447
-
448
- // Check for project-specific paths
449
- if (content.includes(projectPath)) relevance += 10;
450
-
451
- if (relevance >= 3) {
452
- planFile.relevance = relevance;
453
- planFile.content = content;
454
- relevantPlans.push(planFile);
455
- }
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
- }
461
- }
462
- }
463
-
464
- planFiles = relevantPlans;
465
-
466
- if (planFiles.length === 0) return [];
467
-
468
- // 5. Extract decisions from matched plan files
469
- const decisions = [];
470
-
471
- for (const planFile of planFiles) {
472
- try {
473
- const content = planFile.content || await fs.readFile(planFile.path, 'utf-8');
474
-
475
- // Extract "Decisions" sections
476
- const decisionPatterns = [
477
- /## Decisions?\s*(?:Confirmed|Made)?\s*\n([\s\S]*?)(?=\n## |\n---|\Z)/gi,
478
- /\| Decision \|[\s\S]*?\n((?:\|[^\n]+\n)+)/gi
479
- ];
480
-
481
- for (const pattern of decisionPatterns) {
482
- let match;
483
- while ((match = pattern.exec(content)) !== null) {
484
- const section = match[1] || match[0];
485
- // Parse table rows or bullet points
486
- const lines = section.split('\n').filter(l =>
487
- l.includes('|') && !l.includes('---') && !l.includes('Decision') && l.trim().length > 5
488
- );
489
-
490
- for (const line of lines) {
491
- const cells = line.split('|').map(c => c.trim()).filter(c => c.length > 0);
492
- if (cells.length >= 2) {
493
- decisions.push({
494
- type: 'decision',
495
- element: cells[0].replace(/\*\*/g, ''),
496
- evidence: cells[1].replace(/\*\*/g, ''),
497
- confidence: 0.8,
498
- category: 'architecture',
499
- source: planFile.name
500
- });
501
- }
502
- }
503
- }
504
- }
505
-
506
- // Extract "Recommendation:" lines
507
- const recommendations = content.match(/\*\*Recommendation:?\*\*:?\s*(.+)/gi) || [];
508
- for (const rec of recommendations) {
509
- const cleaned = rec.replace(/\*\*Recommendation:?\*\*:?\s*/i, '').trim();
510
- if (cleaned.length > 10) {
511
- decisions.push({
512
- type: 'decision',
513
- element: cleaned,
514
- evidence: `From plan: ${planFile.name}`,
515
- confidence: 0.7,
516
- category: 'architecture',
517
- source: planFile.name
518
- });
519
- }
520
- }
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
- }
526
- }
527
- }
528
-
529
- // Deduplicate by element name (keep longest evidence)
530
- const unique = {};
531
- for (const d of decisions) {
532
- if (!unique[d.element] || d.evidence.length > unique[d.element].evidence.length) {
533
- unique[d.element] = d;
534
- }
535
- }
536
-
537
- return Object.values(unique);
538
- }
539
-
540
- /**
541
- * Promote plan decisions to standards
542
- * Always writes to .mindmeld/decisions/ (local staging)
543
- * Community promotion happens via API → PR to EquilateralAgents-Community-Standards
544
- */
545
- async function promoteDecisions(projectPath, decisions, options = {}) {
546
- if (decisions.length === 0) return [];
547
-
548
- const decisionsDir = path.join(projectPath, '.mindmeld', 'decisions');
549
- await fs.mkdir(decisionsDir, { recursive: true });
550
-
551
- const promoted = [];
552
-
553
- for (const decision of decisions) {
554
- const filename = decision.element
555
- .toLowerCase()
556
- .replace(/[^a-z0-9]+/g, '_')
557
- .replace(/^_|_$/g, '')
558
- .substring(0, 50) + '.md';
559
-
560
- const filePath = path.join(decisionsDir, filename);
561
-
562
- // Don't overwrite existing
563
- try {
564
- await fs.access(filePath);
565
- continue;
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
- }
571
- }
572
-
573
- const content = [
574
- `# ${decision.element}`,
575
- '',
576
- `**Status**: solidified`,
577
- `**Category**: ${decision.category}`,
578
- `**Confidence**: ${(decision.confidence * 100).toFixed(0)}%`,
579
- `**Source**: plan-harvest`,
580
- `**Detected**: ${new Date().toISOString().split('T')[0]}`,
581
- '',
582
- '## Decision',
583
- '',
584
- `- ${decision.evidence}`,
585
- '',
586
- '## Context',
587
- '',
588
- `Extracted from: ${decision.source}`,
589
- '',
590
- '---',
591
- '*Architectural decision - do not revisit without explicit discussion.*'
592
- ].join('\n');
593
-
594
- await fs.writeFile(filePath, content);
595
- promoted.push({ decision, path: filePath });
596
- }
597
-
598
- return promoted;
599
- }
600
-
601
- module.exports = { harvest, promotePatterns, promoteDecisions, harvestPlans, getGitHistory, detectPatterns, savePatterns, showHarvestHelp };