@equilateral_ai/mindmeld 3.3.1 → 3.4.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 (69) hide show
  1. package/README.md +1 -10
  2. package/hooks/pre-compact.js +213 -25
  3. package/hooks/session-start.js +635 -41
  4. package/hooks/subagent-start.js +150 -0
  5. package/hooks/subagent-stop.js +184 -0
  6. package/package.json +8 -7
  7. package/scripts/init-project.js +74 -33
  8. package/scripts/mcp-bridge.js +220 -0
  9. package/src/core/CorrelationAnalyzer.js +157 -0
  10. package/src/core/LLMPatternDetector.js +198 -0
  11. package/src/core/RelevanceDetector.js +123 -36
  12. package/src/core/StandardsIngestion.js +119 -18
  13. package/src/handlers/activity/activityGetMe.js +1 -1
  14. package/src/handlers/activity/activityGetTeam.js +100 -55
  15. package/src/handlers/admin/adminSetup.js +216 -0
  16. package/src/handlers/alerts/alertsAcknowledge.js +6 -6
  17. package/src/handlers/alerts/alertsGet.js +11 -11
  18. package/src/handlers/analytics/activitySummaryGet.js +34 -35
  19. package/src/handlers/analytics/coachingGet.js +11 -11
  20. package/src/handlers/analytics/convergenceGet.js +236 -0
  21. package/src/handlers/analytics/developerScoreGet.js +41 -111
  22. package/src/handlers/collaborators/collaboratorInvite.js +1 -1
  23. package/src/handlers/company/companyUsersDelete.js +141 -0
  24. package/src/handlers/company/companyUsersGet.js +90 -0
  25. package/src/handlers/company/companyUsersPost.js +267 -0
  26. package/src/handlers/company/companyUsersPut.js +76 -0
  27. package/src/handlers/correlations/correlationsDeveloperGet.js +12 -12
  28. package/src/handlers/correlations/correlationsGet.js +8 -8
  29. package/src/handlers/correlations/correlationsProjectGet.js +5 -5
  30. package/src/handlers/enterprise/controlTowerGet.js +224 -0
  31. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +48 -9
  32. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +1 -3
  33. package/src/handlers/github/githubConnectionStatus.js +1 -1
  34. package/src/handlers/github/githubDiscoverPatterns.js +4 -2
  35. package/src/handlers/github/githubPatternsReview.js +7 -36
  36. package/src/handlers/health/healthGet.js +55 -0
  37. package/src/handlers/helpers/checkSuperAdmin.js +13 -14
  38. package/src/handlers/helpers/subscriptionTiers.js +27 -27
  39. package/src/handlers/mcp/mcpHandler.js +569 -0
  40. package/src/handlers/mcp/mindmeldMcpHandler.js +689 -0
  41. package/src/handlers/notifications/sendNotification.js +18 -18
  42. package/src/handlers/patterns/patternEvaluatePromotionPost.js +173 -0
  43. package/src/handlers/projects/projectCreate.js +124 -10
  44. package/src/handlers/projects/projectDelete.js +4 -4
  45. package/src/handlers/projects/projectGet.js +8 -8
  46. package/src/handlers/projects/projectUpdate.js +4 -4
  47. package/src/handlers/reports/aiLeverage.js +34 -30
  48. package/src/handlers/reports/engineeringInvestment.js +16 -16
  49. package/src/handlers/reports/riskForecast.js +41 -21
  50. package/src/handlers/reports/standardsRoi.js +101 -9
  51. package/src/handlers/scheduled/maturityUpdateJob.js +166 -0
  52. package/src/handlers/sessions/sessionStandardsPost.js +43 -7
  53. package/src/handlers/standards/discoveriesGet.js +93 -0
  54. package/src/handlers/standards/projectStandardsGet.js +2 -2
  55. package/src/handlers/standards/projectStandardsPut.js +2 -2
  56. package/src/handlers/standards/standardsRelevantPost.js +107 -12
  57. package/src/handlers/standards/standardsTransition.js +112 -15
  58. package/src/handlers/stripe/billingPortalPost.js +1 -1
  59. package/src/handlers/stripe/enterpriseCheckoutPost.js +2 -2
  60. package/src/handlers/stripe/subscriptionCreatePost.js +2 -2
  61. package/src/handlers/stripe/webhookPost.js +42 -14
  62. package/src/handlers/user/apiTokenCreate.js +71 -0
  63. package/src/handlers/user/apiTokenList.js +64 -0
  64. package/src/handlers/user/userSplashGet.js +90 -73
  65. package/src/handlers/users/cognitoPostConfirmation.js +37 -1
  66. package/src/handlers/users/cognitoPreSignUp.js +114 -0
  67. package/src/handlers/users/userGet.js +12 -8
  68. package/src/handlers/webhooks/githubWebhook.js +117 -125
  69. package/src/index.js +8 -5
package/README.md CHANGED
@@ -209,11 +209,6 @@ npm run test:hooks
209
209
  npm run test:session-start
210
210
  npm run test:pre-compact
211
211
 
212
- # Test standards parsing (no database)
213
- node scripts/demo-standards-parsing.js
214
-
215
- # Test standards ingestion (requires database)
216
- node scripts/test-standards-ingestion.js
217
212
  ```
218
213
 
219
214
  ### Prerequisites
@@ -246,11 +241,7 @@ Rapport integrates with `.equilateral-standards/` to provide context-aware stand
246
241
 
247
242
  **Test it**:
248
243
  ```bash
249
- # Demo parsing (no database required)
250
- node scripts/demo-standards-parsing.js
251
-
252
- # Full ingestion test (requires database)
253
- node scripts/test-standards-ingestion.js
244
+ node scripts/ingest-standards.js
254
245
  ```
255
246
 
256
247
  ## Value Proposition
@@ -66,10 +66,27 @@ async function loadAuthToken() {
66
66
  }
67
67
 
68
68
  /**
69
- * Load Cognito config from .myworld.json
69
+ * Load Cognito config
70
+ * Priority: .mindmeld/config.json → .myworld.json → production defaults
70
71
  * @returns {Promise<Object>} Cognito constructor options or empty object
71
72
  */
72
73
  async function loadCognitoConfig() {
74
+ // 1. Check .mindmeld/config.json first (always points to prod)
75
+ try {
76
+ const mindmeldConfigPath = path.join(process.cwd(), '.mindmeld', 'config.json');
77
+ const content = await fs.readFile(mindmeldConfigPath, 'utf-8');
78
+ const config = JSON.parse(content);
79
+ if (config.auth?.cognitoDomain && config.auth?.cognitoClientId) {
80
+ return {
81
+ cognitoDomain: config.auth.cognitoDomain,
82
+ cognitoClientId: config.auth.cognitoClientId
83
+ };
84
+ }
85
+ } catch (error) {
86
+ // No .mindmeld/config.json or no auth section
87
+ }
88
+
89
+ // 2. Fallback to .myworld.json (may point to dev)
73
90
  try {
74
91
  const configPath = path.join(process.cwd(), '.myworld.json');
75
92
  const content = await fs.readFile(configPath, 'utf-8');
@@ -88,10 +105,24 @@ async function loadCognitoConfig() {
88
105
  }
89
106
 
90
107
  /**
91
- * Load API configuration from .myworld.json
108
+ * Load API configuration
109
+ * Priority: .mindmeld/config.json → .myworld.json → env var → production default
92
110
  * @returns {Promise<{apiUrl: string}>}
93
111
  */
94
112
  async function loadApiConfig() {
113
+ // 1. Check .mindmeld/config.json first (always points to prod)
114
+ try {
115
+ const mindmeldConfigPath = path.join(process.cwd(), '.mindmeld', 'config.json');
116
+ const content = await fs.readFile(mindmeldConfigPath, 'utf-8');
117
+ const config = JSON.parse(content);
118
+ if (config.apiUrl) {
119
+ return { apiUrl: config.apiUrl };
120
+ }
121
+ } catch (error) {
122
+ // No .mindmeld/config.json or no apiUrl
123
+ }
124
+
125
+ // 2. Fallback to .myworld.json (may point to dev)
95
126
  try {
96
127
  const configPath = path.join(process.cwd(), '.myworld.json');
97
128
  const content = await fs.readFile(configPath, 'utf-8');
@@ -134,9 +165,22 @@ async function harvestPatterns(sessionTranscript) {
134
165
  apiUrl: apiConfig.apiUrl
135
166
  });
136
167
 
137
- // Extract session metadata
138
- const sessionId = sessionTranscript.sessionId || generateSessionId();
139
- const userId = sessionTranscript.userId || process.env.USER || 'unknown';
168
+ // Load project context so reinforcePattern/recordViolation have project_id
169
+ await mindmeld.detectProject();
170
+
171
+ // Load session context persisted by session-start hook
172
+ let sessionContext = null;
173
+ try {
174
+ const sessionContextPath = path.join(process.cwd(), '.mindmeld', 'current-session.json');
175
+ const content = await fs.readFile(sessionContextPath, 'utf-8');
176
+ sessionContext = JSON.parse(content);
177
+ } catch (err) {
178
+ // Expected: file may not exist if session-start didn't run
179
+ }
180
+
181
+ // Extract session metadata — prefer persisted session context
182
+ const sessionId = sessionContext?.sessionId || sessionTranscript.sessionId || generateSessionId();
183
+ const userId = sessionContext?.userEmail || sessionTranscript.userId || process.env.USER || 'unknown';
140
184
 
141
185
  // 1. Detect patterns from session (LLM-powered or regex fallback)
142
186
  let patterns = [];
@@ -216,27 +260,18 @@ async function harvestPatterns(sessionTranscript) {
216
260
  // 5. Check for promotion candidates
217
261
  const candidates = await checkPromotionCandidates(mindmeld, validationResults.valid);
218
262
 
219
- // 6. Check README staleness (documentation maintenance)
220
- let readmeStatus = null;
263
+ // 6. Harvest plans from ~/.claude/plans/
264
+ let harvestedPlans = [];
221
265
  try {
222
- const { checkReadmeStaleness } = require('../scripts/check-readme-staleness');
223
- readmeStatus = await checkReadmeStaleness(process.cwd());
224
-
225
- if (readmeStatus.stale) {
226
- console.error(`[MindMeld] 📄 README staleness detected:`);
227
- console.error(`[MindMeld] Changes since last update: ${readmeStatus.changes}`);
228
- console.error(`[MindMeld] Agent changes: ${readmeStatus.analysis.agentChanges}`);
229
- console.error(`[MindMeld] Standards changes: ${readmeStatus.analysis.standardsChanges}`);
230
- console.error(`[MindMeld] Significance: ${readmeStatus.analysis.significance}`);
231
-
232
- if (readmeStatus.shouldTrigger) {
233
- console.error(`[MindMeld] 🔧 Critical changes detected - README update recommended`);
234
- console.error(`[MindMeld] Run: node -e "const L=require('./src/agents/specialists/LibrarianAgent');new L().generateProjectReadme({projectRoot:process.cwd()})"`);
266
+ harvestedPlans = await harvestPlans(sessionTranscript);
267
+ if (harvestedPlans.length > 0) {
268
+ console.error(`[MindMeld] Harvested ${harvestedPlans.length} plan(s) from session`);
269
+ for (const plan of harvestedPlans) {
270
+ console.error(` - ${plan.title} (${plan.filename})`);
235
271
  }
236
272
  }
237
273
  } catch (error) {
238
- // Non-fatal - don't break pattern harvesting
239
- console.error(`[MindMeld] README check skipped:`, error.message);
274
+ console.error('[MindMeld] Plan harvesting failed (non-fatal):', error.message);
240
275
  }
241
276
 
242
277
  // 7. Log results
@@ -246,8 +281,10 @@ async function harvestPatterns(sessionTranscript) {
246
281
  violations: validationResults.violations.length,
247
282
  reinforced: validationResults.valid.length,
248
283
  promotionCandidates: candidates.length,
249
- readmeStale: readmeStatus ? readmeStatus.stale : null,
250
- readmeUpdateRecommended: readmeStatus ? readmeStatus.shouldTrigger : false,
284
+ plansHarvested: harvestedPlans.length,
285
+ plans: harvestedPlans,
286
+ readmeStale: null,
287
+ readmeUpdateRecommended: false,
251
288
  llmUsed: useLLM && llmAnalysis?.success,
252
289
  llmModel: llmAnalysis?.model || null,
253
290
  llmSummary: llmAnalysis?.summary || null,
@@ -279,6 +316,133 @@ async function harvestPatterns(sessionTranscript) {
279
316
  }
280
317
  }
281
318
 
319
+ /**
320
+ * Harvest plans from ~/.claude/plans/
321
+ * Scans for plan files modified during this session (within last 4 hours)
322
+ * Extracts title, project references, and key decisions
323
+ *
324
+ * @param {Object} sessionTranscript - Session data (may contain session start time)
325
+ * @returns {Promise<Array>} Array of harvested plan objects
326
+ */
327
+ async function harvestPlans(sessionTranscript) {
328
+ const os = require('os');
329
+ const plansDir = path.join(os.homedir(), '.claude', 'plans');
330
+
331
+ let files;
332
+ try {
333
+ files = await fs.readdir(plansDir);
334
+ } catch (error) {
335
+ if (error.code === 'ENOENT') return [];
336
+ throw error;
337
+ }
338
+
339
+ const mdFiles = files.filter(f => f.endsWith('.md'));
340
+ if (mdFiles.length === 0) return [];
341
+
342
+ // Plans modified in the last 4 hours are considered session-relevant
343
+ const cutoff = Date.now() - (4 * 60 * 60 * 1000);
344
+ const harvested = [];
345
+
346
+ for (const filename of mdFiles) {
347
+ const filePath = path.join(plansDir, filename);
348
+ const stat = await fs.stat(filePath);
349
+
350
+ if (stat.mtimeMs < cutoff) continue;
351
+
352
+ const content = await fs.readFile(filePath, 'utf-8');
353
+ const plan = parsePlanFile(filename, content, stat);
354
+ if (plan) {
355
+ harvested.push(plan);
356
+ }
357
+ }
358
+
359
+ // Sort by most recently modified first
360
+ harvested.sort((a, b) => b.modifiedAt - a.modifiedAt);
361
+
362
+ return harvested;
363
+ }
364
+
365
+ /**
366
+ * Parse a plan file into structured data
367
+ * @param {string} filename - Plan filename
368
+ * @param {string} content - Raw markdown content
369
+ * @param {Object} stat - File stat object
370
+ * @returns {Object|null} Parsed plan or null if empty
371
+ */
372
+ function parsePlanFile(filename, content, stat) {
373
+ if (!content || content.trim().length === 0) return null;
374
+
375
+ const lines = content.split('\n');
376
+
377
+ // Extract title from first heading
378
+ let title = filename.replace('.md', '');
379
+ for (const line of lines) {
380
+ const headingMatch = line.match(/^#\s+(?:Plan:\s*)?(.+)/);
381
+ if (headingMatch) {
382
+ title = headingMatch[1].trim();
383
+ break;
384
+ }
385
+ }
386
+
387
+ // Extract sections
388
+ const sections = {};
389
+ let currentSection = null;
390
+ let currentContent = [];
391
+
392
+ for (const line of lines) {
393
+ const sectionMatch = line.match(/^##\s+(.+)/);
394
+ if (sectionMatch) {
395
+ if (currentSection) {
396
+ sections[currentSection] = currentContent.join('\n').trim();
397
+ }
398
+ currentSection = sectionMatch[1].trim().toLowerCase();
399
+ currentContent = [];
400
+ } else if (currentSection) {
401
+ currentContent.push(line);
402
+ }
403
+ }
404
+ if (currentSection) {
405
+ sections[currentSection] = currentContent.join('\n').trim();
406
+ }
407
+
408
+ // Extract file paths mentioned (src/..., hooks/..., etc.)
409
+ const fileRefs = [];
410
+ const filePattern = /(?:^|\s|`)((?:src|hooks|scripts|frontend|lib|test|tests)\/[\w/./-]+\.\w+)/g;
411
+ let match;
412
+ while ((match = filePattern.exec(content)) !== null) {
413
+ if (!fileRefs.includes(match[1])) {
414
+ fileRefs.push(match[1]);
415
+ }
416
+ }
417
+
418
+ // Detect which project this plan relates to
419
+ const projectHints = [];
420
+ const projectPatterns = [
421
+ /rapport/i, /mindmeld/i, /equilateral/i, /jarvis/i,
422
+ /honeydo/i, /timebridge/i, /powerspec/i
423
+ ];
424
+ for (const pp of projectPatterns) {
425
+ if (pp.test(content)) {
426
+ projectHints.push(pp.source.replace(/\/i$/, ''));
427
+ }
428
+ }
429
+
430
+ return {
431
+ filename: filename,
432
+ planId: filename.replace('.md', ''),
433
+ title: title,
434
+ modifiedAt: stat.mtimeMs,
435
+ modifiedIso: stat.mtime.toISOString(),
436
+ sizeBytes: stat.size,
437
+ lineCount: lines.length,
438
+ sections: Object.keys(sections),
439
+ context: sections['context'] || null,
440
+ filesReferenced: fileRefs.slice(0, 20),
441
+ projectHints: projectHints,
442
+ content: content
443
+ };
444
+ }
445
+
282
446
  /**
283
447
  * Check if MindMeld is configured for this project
284
448
  */
@@ -405,6 +569,30 @@ async function generatePostCompactContext(summary, llmAnalysis) {
405
569
  sections.push('');
406
570
  }
407
571
 
572
+ // Include active plans from this session
573
+ if (summary.plans && summary.plans.length > 0) {
574
+ sections.push('## Active Plans');
575
+ for (const plan of summary.plans) {
576
+ sections.push(`### ${plan.title}`);
577
+ sections.push(`- **File**: \`~/.claude/plans/${plan.filename}\``);
578
+ sections.push(`- **Modified**: ${plan.modifiedIso}`);
579
+ if (plan.projectHints.length > 0) {
580
+ sections.push(`- **Projects**: ${plan.projectHints.join(', ')}`);
581
+ }
582
+ if (plan.filesReferenced.length > 0) {
583
+ sections.push(`- **Files**: ${plan.filesReferenced.slice(0, 10).join(', ')}`);
584
+ }
585
+ if (plan.context) {
586
+ // Include first 500 chars of context section
587
+ const contextPreview = plan.context.length > 500
588
+ ? plan.context.substring(0, 500) + '...'
589
+ : plan.context;
590
+ sections.push(`- **Context**: ${contextPreview}`);
591
+ }
592
+ sections.push('');
593
+ }
594
+ }
595
+
408
596
  // Load relevant standards from .equilateral-standards if available
409
597
  const standardsPath = path.join(process.cwd(), '.equilateral-standards');
410
598
  try {
@@ -492,4 +680,4 @@ if (require.main === module) {
492
680
  });
493
681
  }
494
682
 
495
- module.exports = { harvestPatterns, parseSessionTranscript, generatePostCompactContext };
683
+ module.exports = { harvestPatterns, harvestPlans, parseSessionTranscript, generatePostCompactContext };