@equilateral_ai/mindmeld 3.2.0 → 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 (68) hide show
  1. package/README.md +4 -4
  2. package/hooks/README.md +46 -4
  3. package/hooks/pre-compact.js +87 -1
  4. package/hooks/session-end.js +292 -0
  5. package/hooks/session-start.js +292 -23
  6. package/package.json +4 -2
  7. package/scripts/auth-login.js +53 -0
  8. package/scripts/init-project.js +69 -375
  9. package/src/core/AuthManager.js +498 -0
  10. package/src/core/CrossReferenceEngine.js +624 -0
  11. package/src/core/DeprecationScheduler.js +183 -0
  12. package/src/core/LLMPatternDetector.js +218 -0
  13. package/src/core/RapportOrchestrator.js +186 -0
  14. package/src/core/RelevanceDetector.js +32 -2
  15. package/src/core/StandardLifecycle.js +244 -0
  16. package/src/core/StandardsIngestion.js +341 -28
  17. package/src/core/parsers/adrParser.js +479 -0
  18. package/src/core/parsers/cursorRulesParser.js +564 -0
  19. package/src/core/parsers/eslintParser.js +439 -0
  20. package/src/handlers/alerts/alertsAcknowledge.js +4 -3
  21. package/src/handlers/analytics/activitySummaryGet.js +235 -0
  22. package/src/handlers/analytics/coachingGet.js +361 -0
  23. package/src/handlers/analytics/developerScoreGet.js +207 -0
  24. package/src/handlers/collaborators/collaboratorAdd.js +4 -5
  25. package/src/handlers/collaborators/collaboratorInvite.js +6 -5
  26. package/src/handlers/collaborators/collaboratorList.js +3 -3
  27. package/src/handlers/collaborators/collaboratorRemove.js +5 -4
  28. package/src/handlers/correlations/correlationsDeveloperGet.js +12 -11
  29. package/src/handlers/correlations/correlationsGet.js +1 -1
  30. package/src/handlers/correlations/correlationsProjectGet.js +7 -6
  31. package/src/handlers/enterprise/enterpriseAuditGet.js +108 -0
  32. package/src/handlers/enterprise/enterpriseContributorsGet.js +85 -0
  33. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +53 -0
  34. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +77 -0
  35. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +71 -0
  36. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +87 -0
  37. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +122 -0
  38. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +77 -0
  39. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +138 -0
  40. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +89 -0
  41. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +90 -0
  42. package/src/handlers/github/githubConnectionStatus.js +1 -1
  43. package/src/handlers/github/githubDiscoverPatterns.js +264 -5
  44. package/src/handlers/github/githubOAuthCallback.js +14 -2
  45. package/src/handlers/github/githubOAuthStart.js +1 -1
  46. package/src/handlers/github/githubPatternsReview.js +1 -1
  47. package/src/handlers/github/githubReposList.js +1 -1
  48. package/src/handlers/helpers/auditLogger.js +201 -0
  49. package/src/handlers/helpers/index.js +19 -1
  50. package/src/handlers/helpers/lambdaWrapper.js +1 -1
  51. package/src/handlers/notifications/sendNotification.js +1 -1
  52. package/src/handlers/projects/projectCreate.js +28 -1
  53. package/src/handlers/projects/projectDelete.js +3 -3
  54. package/src/handlers/projects/projectUpdate.js +4 -5
  55. package/src/handlers/scheduled/analyzeCorrelations.js +3 -3
  56. package/src/handlers/scheduled/generateAlerts.js +1 -1
  57. package/src/handlers/standards/catalogGet.js +185 -0
  58. package/src/handlers/standards/catalogSync.js +120 -0
  59. package/src/handlers/standards/projectStandardsGet.js +135 -0
  60. package/src/handlers/standards/projectStandardsPut.js +131 -0
  61. package/src/handlers/standards/standardsAuditGet.js +65 -0
  62. package/src/handlers/standards/standardsParseUpload.js +153 -0
  63. package/src/handlers/standards/standardsRelevantPost.js +213 -0
  64. package/src/handlers/standards/standardsTransition.js +64 -0
  65. package/src/handlers/user/userSplashAck.js +91 -0
  66. package/src/handlers/user/userSplashGet.js +194 -0
  67. package/src/handlers/users/userProfilePut.js +77 -0
  68. package/src/index.js +37 -29
@@ -0,0 +1,183 @@
1
+ /**
2
+ * DeprecationScheduler.js - Scheduled Deprecation Management
3
+ *
4
+ * Handles the scheduled deprecation workflow for standards:
5
+ * 1. scheduleDeprecation - Marks a standard as deprecated with a grace period
6
+ * 2. processScheduledDeprecations - Deletes standards past their grace period
7
+ * 3. cancelDeprecation - Restores a deprecated standard back to active
8
+ *
9
+ * Called by scheduled Lambda / EventBridge for automatic cleanup,
10
+ * or directly by handlers for user-initiated actions.
11
+ */
12
+
13
+ const { executeQuery } = require('../handlers/helpers/dbOperations');
14
+ const { StandardLifecycle, STATES } = require('./StandardLifecycle');
15
+
16
+ class DeprecationScheduler {
17
+ constructor(config = {}) {
18
+ this.config = {
19
+ defaultGracePeriodDays: config.defaultGracePeriodDays || 30,
20
+ ...config
21
+ };
22
+
23
+ this.lifecycle = config.lifecycle || new StandardLifecycle();
24
+ }
25
+
26
+ /**
27
+ * Schedule a standard for deprecation with a grace period
28
+ *
29
+ * Sets deprecated_at and deprecation_reason on the standard,
30
+ * transitions state to 'deprecated', and records in audit trail.
31
+ *
32
+ * @param {string} standardId - The standard identifier
33
+ * @param {number} gracePeriodDays - Days before automatic deletion
34
+ * @param {string} userId - Email of the user scheduling deprecation
35
+ * @param {string} reason - Reason for deprecation
36
+ * @returns {Promise<Object>} Deprecation result
37
+ */
38
+ async scheduleDeprecation(standardId, gracePeriodDays, userId, reason) {
39
+ const days = gracePeriodDays || this.config.defaultGracePeriodDays;
40
+
41
+ // Execute the lifecycle transition to deprecated
42
+ const transitionResult = await this.lifecycle.transition(
43
+ standardId,
44
+ 'deprecate',
45
+ userId,
46
+ reason
47
+ );
48
+
49
+ // Set deprecation metadata on the standard
50
+ const deprecatedAt = new Date();
51
+ const deletionDate = new Date(deprecatedAt.getTime() + (days * 24 * 60 * 60 * 1000));
52
+
53
+ await executeQuery(`
54
+ UPDATE rapport.patterns
55
+ SET
56
+ deprecated_at = $2,
57
+ deprecation_reason = $3
58
+ WHERE pattern_id = $1
59
+ `, [standardId, deprecatedAt, reason]);
60
+
61
+ console.log(
62
+ `[DeprecationScheduler] Scheduled ${standardId} for deletion on ${deletionDate.toISOString()} ` +
63
+ `(${days} day grace period)`
64
+ );
65
+
66
+ return {
67
+ standard_id: standardId,
68
+ old_state: transitionResult.old_state,
69
+ new_state: transitionResult.new_state,
70
+ deprecated_at: deprecatedAt.toISOString(),
71
+ grace_period_days: days,
72
+ scheduled_deletion: deletionDate.toISOString(),
73
+ reason: reason,
74
+ audit_entry: transitionResult.audit_entry
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Process all standards that have passed their grace period
80
+ *
81
+ * Finds deprecated standards where deprecated_at + grace period < now,
82
+ * and transitions them to 'deleted'.
83
+ *
84
+ * Intended to be called by a scheduled Lambda / EventBridge rule.
85
+ *
86
+ * @returns {Promise<Object>} Processing results
87
+ */
88
+ async processScheduledDeprecations() {
89
+ const gracePeriodDays = this.config.defaultGracePeriodDays;
90
+
91
+ // Find standards past their grace period
92
+ const result = await executeQuery(`
93
+ SELECT pattern_id, deprecated_at, deprecation_reason
94
+ FROM rapport.patterns
95
+ WHERE lifecycle_state = $1
96
+ AND deprecated_at IS NOT NULL
97
+ AND deprecated_at + ($2 || ' days')::INTERVAL < NOW()
98
+ `, [STATES.DEPRECATED, gracePeriodDays]);
99
+
100
+ const standards = result.rows;
101
+
102
+ console.log(`[DeprecationScheduler] Found ${standards.length} standards past grace period`);
103
+
104
+ const processed = [];
105
+ const errors = [];
106
+
107
+ for (const standard of standards) {
108
+ try {
109
+ const transitionResult = await this.lifecycle.transition(
110
+ standard.pattern_id,
111
+ 'delete',
112
+ 'system@mindmeld.dev',
113
+ `Automatic deletion after ${gracePeriodDays}-day grace period. ` +
114
+ `Original reason: ${standard.deprecation_reason || 'Not specified'}`
115
+ );
116
+
117
+ processed.push({
118
+ standard_id: standard.pattern_id,
119
+ deprecated_at: standard.deprecated_at,
120
+ deleted_at: new Date().toISOString()
121
+ });
122
+
123
+ console.log(`[DeprecationScheduler] Deleted ${standard.pattern_id} (deprecated ${standard.deprecated_at})`);
124
+ } catch (error) {
125
+ console.error(`[DeprecationScheduler] Failed to delete ${standard.pattern_id}:`, error.message);
126
+ errors.push({
127
+ standard_id: standard.pattern_id,
128
+ error: error.message
129
+ });
130
+ }
131
+ }
132
+
133
+ return {
134
+ total_found: standards.length,
135
+ processed: processed.length,
136
+ errors: errors.length,
137
+ deleted: processed,
138
+ failed: errors
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Cancel a scheduled deprecation and restore to active
144
+ *
145
+ * Transitions the standard back to 'active', clears deprecated_at
146
+ * and deprecation_reason, and records in audit trail.
147
+ *
148
+ * @param {string} standardId - The standard identifier
149
+ * @param {string} userId - Email of the user cancelling deprecation
150
+ * @param {string} reason - Reason for cancelling deprecation
151
+ * @returns {Promise<Object>} Cancellation result
152
+ */
153
+ async cancelDeprecation(standardId, userId, reason) {
154
+ // Execute the lifecycle transition back to active
155
+ const transitionResult = await this.lifecycle.transition(
156
+ standardId,
157
+ 'cancel_deprecation',
158
+ userId,
159
+ reason
160
+ );
161
+
162
+ // Clear deprecation metadata
163
+ await executeQuery(`
164
+ UPDATE rapport.patterns
165
+ SET
166
+ deprecated_at = NULL,
167
+ deprecation_reason = NULL
168
+ WHERE pattern_id = $1
169
+ `, [standardId]);
170
+
171
+ console.log(`[DeprecationScheduler] Cancelled deprecation for ${standardId} by ${userId}`);
172
+
173
+ return {
174
+ standard_id: standardId,
175
+ old_state: transitionResult.old_state,
176
+ new_state: transitionResult.new_state,
177
+ reason: reason,
178
+ audit_entry: transitionResult.audit_entry
179
+ };
180
+ }
181
+ }
182
+
183
+ module.exports = { DeprecationScheduler };
@@ -503,6 +503,224 @@ Respond with a JSON object: { "summary": "...", "keyDecisions": ["..."], "newPat
503
503
  clearCache() {
504
504
  this.cache.clear();
505
505
  }
506
+
507
+ /**
508
+ * Analyze repository code samples for patterns (used in GitHub discovery)
509
+ * Returns patterns in YAML standards format with action types
510
+ *
511
+ * @param {string} codeContext - Combined code samples with file paths
512
+ * @param {Object} context - Additional context (tech stack, frameworks)
513
+ * @returns {Promise<Object>} Patterns in YAML-compatible format
514
+ */
515
+ async analyzeRepoPatterns(codeContext, context = {}) {
516
+ if (!this.isAvailable()) {
517
+ return this.fallbackRepoAnalysis(codeContext, context);
518
+ }
519
+
520
+ const systemPrompt = this.buildRepoAnalysisSystemPrompt(context);
521
+ const userPrompt = this.buildRepoAnalysisUserPrompt(codeContext, context);
522
+
523
+ try {
524
+ const requestBody = {
525
+ anthropic_version: 'bedrock-2023-05-31',
526
+ max_tokens: this.config.maxTokens,
527
+ system: systemPrompt,
528
+ messages: [{ role: 'user', content: userPrompt }]
529
+ };
530
+
531
+ const command = new InvokeModelCommand({
532
+ modelId: this.config.model,
533
+ contentType: 'application/json',
534
+ accept: 'application/json',
535
+ body: JSON.stringify(requestBody)
536
+ });
537
+
538
+ const response = await this.client.send(command);
539
+ const responseBody = JSON.parse(new TextDecoder().decode(response.body));
540
+
541
+ return this.parseRepoAnalysisResponse(responseBody.content?.[0]?.text || '');
542
+ } catch (error) {
543
+ console.error('[LLMPatternDetector] Repo analysis error:', error.message);
544
+ return this.fallbackRepoAnalysis(codeContext, context);
545
+ }
546
+ }
547
+
548
+ /**
549
+ * Build system prompt for repository pattern analysis
550
+ */
551
+ buildRepoAnalysisSystemPrompt(context) {
552
+ const techStack = context.techStack?.join(', ') || 'Unknown';
553
+ const frameworks = context.frameworks?.join(', ') || 'Unknown';
554
+
555
+ return `You are an expert code analyst for MindMeld, a standards and pattern management platform.
556
+
557
+ Your task is to analyze code samples from a repository and extract coding patterns, conventions, and potential issues in a format compatible with our YAML standards schema.
558
+
559
+ Context about this repository:
560
+ - Detected tech stack: ${techStack}
561
+ - Detected frameworks: ${frameworks}
562
+
563
+ Extract patterns using these action types:
564
+ - ALWAYS: Rules that must always be followed (critical patterns)
565
+ - NEVER: Anti-patterns that must be avoided
566
+ - USE: Recommended tools, libraries, or approaches
567
+ - PREFER: Better alternatives when possible
568
+ - AVOID: Patterns to avoid when better alternatives exist
569
+
570
+ Respond ONLY with valid JSON in this exact format:
571
+ {
572
+ "patterns": [
573
+ {
574
+ "element": "Short pattern name",
575
+ "action": "ALWAYS|NEVER|USE|PREFER|AVOID",
576
+ "rule": "Clear description of the rule",
577
+ "category": "api|database|security|performance|architecture|testing|error-handling",
578
+ "confidence": 0.95,
579
+ "evidence": "Code snippet or reference showing this pattern"
580
+ }
581
+ ],
582
+ "anti_patterns": [
583
+ {
584
+ "pattern": "Description of the anti-pattern",
585
+ "risk": "Why this is problematic",
586
+ "fix": "How to fix it"
587
+ }
588
+ ],
589
+ "recommendations": [
590
+ {
591
+ "category": "standards-category-name",
592
+ "reason": "Why this standards category is relevant",
593
+ "priority": "high|medium|low"
594
+ }
595
+ ],
596
+ "tech_summary": {
597
+ "primary_language": "JavaScript/TypeScript/Python/etc",
598
+ "architecture": "serverless|monolith|microservices|frontend-spa",
599
+ "key_patterns": ["pattern1", "pattern2"]
600
+ }
601
+ }`;
602
+ }
603
+
604
+ /**
605
+ * Build user prompt for repository analysis
606
+ */
607
+ buildRepoAnalysisUserPrompt(codeContext, context) {
608
+ const repoName = context.repoName || 'Unknown repository';
609
+
610
+ return `Analyze these code samples from "${repoName}" and extract patterns:
611
+
612
+ ${codeContext}
613
+
614
+ ---
615
+
616
+ Focus on identifying:
617
+ 1. Database connection patterns (pooling vs single client, connection reuse)
618
+ 2. Error handling approaches (try/catch, wrapper functions, error responses)
619
+ 3. API design patterns (response formats, authentication, CORS)
620
+ 4. Security patterns (input validation, secrets management, authorization)
621
+ 5. Code organization (helper functions, modular structure)
622
+ 6. Testing patterns (test frameworks, mocking approaches)
623
+
624
+ Extract actionable patterns that could become team standards.
625
+ Respond with JSON only.`;
626
+ }
627
+
628
+ /**
629
+ * Parse repository analysis response
630
+ */
631
+ parseRepoAnalysisResponse(content) {
632
+ try {
633
+ let jsonStr = content;
634
+ const jsonMatch = content.match(/```(?:json)?\s*([\s\S]*?)```/);
635
+ if (jsonMatch) {
636
+ jsonStr = jsonMatch[1];
637
+ }
638
+
639
+ const parsed = JSON.parse(jsonStr.trim());
640
+
641
+ return {
642
+ success: true,
643
+ source: 'llm_repo_analysis',
644
+ model: this.config.model,
645
+ timestamp: new Date().toISOString(),
646
+ patterns: parsed.patterns || [],
647
+ anti_patterns: parsed.anti_patterns || [],
648
+ recommendations: parsed.recommendations || [],
649
+ tech_summary: parsed.tech_summary || {}
650
+ };
651
+ } catch (error) {
652
+ console.error('[LLMPatternDetector] Failed to parse repo analysis:', error.message);
653
+ return {
654
+ success: false,
655
+ source: 'llm_repo_analysis',
656
+ error: 'Failed to parse LLM response',
657
+ rawContent: content.substring(0, 500),
658
+ patterns: [],
659
+ anti_patterns: [],
660
+ recommendations: []
661
+ };
662
+ }
663
+ }
664
+
665
+ /**
666
+ * Fallback analysis for repository patterns when LLM unavailable
667
+ */
668
+ fallbackRepoAnalysis(codeContext, context) {
669
+ const patterns = [];
670
+ const antiPatterns = [];
671
+
672
+ // Basic regex detection for common patterns
673
+ if (/wrapHandler/.test(codeContext)) {
674
+ patterns.push({
675
+ element: 'Lambda Handler Wrapper',
676
+ action: 'USE',
677
+ rule: 'Use wrapHandler for consistent Lambda error handling',
678
+ category: 'api',
679
+ confidence: 0.8,
680
+ evidence: 'Found wrapHandler usage'
681
+ });
682
+ }
683
+
684
+ if (/executeQuery/.test(codeContext)) {
685
+ patterns.push({
686
+ element: 'Database Query Helper',
687
+ action: 'USE',
688
+ rule: 'Use executeQuery for standardized database operations',
689
+ category: 'database',
690
+ confidence: 0.8,
691
+ evidence: 'Found executeQuery usage'
692
+ });
693
+ }
694
+
695
+ if (/new Pool\(|createPool/.test(codeContext)) {
696
+ antiPatterns.push({
697
+ pattern: 'Connection pool in Lambda',
698
+ risk: 'Connection pools are wasteful in Lambda - connections persist across invocations',
699
+ fix: 'Use a cached single client instead of a connection pool'
700
+ });
701
+ }
702
+
703
+ if (/try\s*\{[\s\S]*catch\s*\(/.test(codeContext)) {
704
+ patterns.push({
705
+ element: 'Try-Catch Error Handling',
706
+ action: 'ALWAYS',
707
+ rule: 'Wrap async operations in try-catch blocks',
708
+ category: 'error-handling',
709
+ confidence: 0.7,
710
+ evidence: 'Found try-catch blocks'
711
+ });
712
+ }
713
+
714
+ return {
715
+ success: true,
716
+ source: 'fallback_repo_analysis',
717
+ timestamp: new Date().toISOString(),
718
+ patterns,
719
+ anti_patterns: antiPatterns,
720
+ recommendations: [],
721
+ tech_summary: { primary_language: 'Unknown', architecture: 'Unknown', key_patterns: [] }
722
+ };
723
+ }
506
724
  }
507
725
 
508
726
  module.exports = { LLMPatternDetector };
@@ -394,6 +394,192 @@ class RapportOrchestrator extends EventEmitter {
394
394
  }
395
395
  }
396
396
 
397
+ // =============================================
398
+ // Roadmap Team Dispatch (TeamOrchestrator)
399
+ // =============================================
400
+
401
+ /**
402
+ * Roadmap team templates — each team gets isolated agents and phased workflow
403
+ */
404
+ getRoadmapTeams() {
405
+ return {
406
+ 'standards-parsers': {
407
+ description: 'Standards parsers for onboarding upload (ADR, ESLint, Cursor rules)',
408
+ agents: ['CodeGenerationAgent', 'PatternHarvestingAgent'],
409
+ phases: ['ADR_PARSER', 'ESLINT_PARSER', 'CURSOR_RULES_PARSER', 'CROSS_REFERENCE']
410
+ },
411
+ 'frontend-curation': {
412
+ description: 'Frontend curation dashboard (review queue, rejection flow, conflict resolution)',
413
+ agents: ['UIUXSpecialistAgent', 'CodeGenerationAgent'],
414
+ phases: ['REVIEW_QUEUE', 'REJECTION_FLOW', 'CONFLICT_RESOLUTION', 'STANDARD_EDITOR']
415
+ },
416
+ 'standard-lifecycle': {
417
+ description: 'Standard lifecycle state machine, audit trail, deprecation',
418
+ agents: ['DatabaseAgent', 'AuditorAgent'],
419
+ phases: ['STATE_MACHINE', 'AUDIT_TRAIL_TABLE', 'DEPRECATION_SCHEDULER']
420
+ },
421
+ 'weekly-splash-cli': {
422
+ description: 'Weekly splash screen, activity summary, /mm slash commands',
423
+ agents: ['CodeGenerationAgent', 'KnowledgeSynthesisAgent'],
424
+ phases: ['SPLASH_SCREEN', 'ACTIVITY_SUMMARY', 'SLASH_COMMANDS']
425
+ },
426
+ 'developer-insights': {
427
+ description: 'Enterprise developer insights (quality scoring, trends, coaching)',
428
+ agents: ['BusinessIntelligenceAgent', 'DataGovernanceAgent'],
429
+ phases: ['QUALITY_SCORING', 'TEAM_TRENDS', 'COACHING_ENGINE']
430
+ }
431
+ };
432
+ }
433
+
434
+ /**
435
+ * Dispatch all roadmap teams in parallel
436
+ * Each team runs with isolated context, dedicated agents, phased workflow
437
+ *
438
+ * @param {Object} options - Dispatch options
439
+ * @param {string} options.team - Dispatch a single team by name (optional)
440
+ * @returns {Promise<Object>} Results from all teams
441
+ */
442
+ async dispatchRoadmap(options = {}) {
443
+ const allTeams = this.getRoadmapTeams();
444
+ const teamsToRun = options.team
445
+ ? { [options.team]: allTeams[options.team] }
446
+ : allTeams;
447
+
448
+ if (options.team && !allTeams[options.team]) {
449
+ throw new Error(`Unknown team: ${options.team}. Available: ${Object.keys(allTeams).join(', ')}`);
450
+ }
451
+
452
+ console.log(`[RapportOrchestrator] Dispatching ${Object.keys(teamsToRun).length} roadmap teams in parallel...`);
453
+ this.emit('roadmap:started', { teams: Object.keys(teamsToRun), startedAt: new Date().toISOString() });
454
+
455
+ // Connect to extended agent set for roadmap work
456
+ await this.connectRoadmapAgents(teamsToRun);
457
+
458
+ // Dispatch all teams in parallel
459
+ const teamPromises = Object.entries(teamsToRun).map(([teamName, template]) =>
460
+ this.dispatchTeam(teamName, template, options)
461
+ .then(result => ({ team: teamName, status: 'fulfilled', result }))
462
+ .catch(error => ({ team: teamName, status: 'rejected', error: error.message }))
463
+ );
464
+
465
+ const results = await Promise.all(teamPromises);
466
+
467
+ const summary = {
468
+ total: results.length,
469
+ fulfilled: results.filter(r => r.status === 'fulfilled').length,
470
+ rejected: results.filter(r => r.status === 'rejected').length,
471
+ teams: results,
472
+ completedAt: new Date().toISOString()
473
+ };
474
+
475
+ console.log(`[RapportOrchestrator] Roadmap dispatch complete: ${summary.fulfilled}/${summary.total} teams succeeded`);
476
+ this.emit('roadmap:completed', summary);
477
+
478
+ return summary;
479
+ }
480
+
481
+ /**
482
+ * Dispatch a single team with isolated context
483
+ */
484
+ async dispatchTeam(teamName, template, options = {}) {
485
+ const teamId = `team_${teamName}_${Date.now()}`;
486
+ console.log(`[RapportOrchestrator] Dispatching team: ${teamName} (${teamId})`);
487
+ this.emit('team:started', { teamId, teamName, phases: template.phases });
488
+
489
+ const teamContext = {
490
+ teamId,
491
+ teamName,
492
+ startedAt: new Date().toISOString(),
493
+ projectRoot: this.config.rapportRoot,
494
+ standardsPath: path.join(this.config.rapportRoot, this.config.standardsPath),
495
+ phaseResults: {}
496
+ };
497
+
498
+ // Execute phases sequentially within team
499
+ for (const phase of template.phases) {
500
+ console.log(` [${teamName}] Phase: ${phase}`);
501
+ this.emit('phase:started', { teamId, teamName, phase });
502
+
503
+ try {
504
+ const phaseResult = await this.executeTeamPhase(teamName, phase, template.agents, teamContext);
505
+ teamContext.phaseResults[phase] = { success: true, result: phaseResult };
506
+ this.emit('phase:completed', { teamId, teamName, phase, result: phaseResult });
507
+ } catch (error) {
508
+ console.error(` [${teamName}] Phase ${phase} failed:`, error.message);
509
+ teamContext.phaseResults[phase] = { success: false, error: error.message };
510
+ this.emit('phase:failed', { teamId, teamName, phase, error: error.message });
511
+ }
512
+ }
513
+
514
+ teamContext.completedAt = new Date().toISOString();
515
+ this.emit('team:completed', { teamId, teamName, context: teamContext });
516
+
517
+ return teamContext;
518
+ }
519
+
520
+ /**
521
+ * Execute a single phase within a team, using assigned agents
522
+ */
523
+ async executeTeamPhase(teamName, phase, agentNames, teamContext) {
524
+ // Try each assigned agent for this phase
525
+ for (const agentName of agentNames) {
526
+ if (!this.agents.has(agentName)) continue;
527
+
528
+ try {
529
+ const agent = this.getAgent(agentName);
530
+
531
+ // Try phase-specific method first, then generic execute
532
+ const methodName = `execute${phase.replace(/_/g, '')}`;
533
+ if (typeof agent[methodName] === 'function') {
534
+ return await agent[methodName](teamContext);
535
+ } else if (typeof agent.execute === 'function') {
536
+ return await agent.execute({
537
+ task: phase,
538
+ team: teamName,
539
+ context: teamContext
540
+ });
541
+ }
542
+ } catch (error) {
543
+ console.warn(` [${teamName}] Agent ${agentName} failed on ${phase}:`, error.message);
544
+ }
545
+ }
546
+
547
+ // Fallback: emit phase for external handling
548
+ return { status: 'pending_implementation', phase, team: teamName };
549
+ }
550
+
551
+ /**
552
+ * Connect additional agents needed for roadmap work
553
+ */
554
+ async connectRoadmapAgents(teams) {
555
+ const agentsPath = path.join(this.config.equilateralAgentsPath, 'src/agents/specialists');
556
+ const neededAgents = new Set();
557
+
558
+ for (const template of Object.values(teams)) {
559
+ for (const agentName of template.agents) {
560
+ if (!this.agents.has(agentName)) {
561
+ neededAgents.add(agentName);
562
+ }
563
+ }
564
+ }
565
+
566
+ for (const agentName of neededAgents) {
567
+ try {
568
+ const agentPath = path.join(agentsPath, `${agentName}.js`);
569
+ const AgentClass = require(agentPath);
570
+ this.agents.set(agentName, {
571
+ name: agentName,
572
+ class: AgentClass,
573
+ instance: null,
574
+ status: 'registered'
575
+ });
576
+ console.log(`[RapportOrchestrator] Registered roadmap agent: ${agentName}`);
577
+ } catch (error) {
578
+ console.warn(`[RapportOrchestrator] Could not load roadmap agent ${agentName}:`, error.message);
579
+ }
580
+ }
581
+ }
582
+
397
583
  /**
398
584
  * Get orchestrator status
399
585
  */
@@ -15,6 +15,7 @@
15
15
  const fs = require('fs').promises;
16
16
  const path = require('path');
17
17
  const { executeQuery } = require('../handlers/helpers/dbOperations');
18
+ const { StandardsIngestion } = require('./StandardsIngestion');
18
19
 
19
20
  class RelevanceDetector {
20
21
  constructor(options = {}) {
@@ -459,8 +460,37 @@ class RelevanceDetector {
459
460
  const result = await executeQuery(query, [categories]);
460
461
  return result.rows;
461
462
  } catch (error) {
462
- // If table doesn't exist (Phase 1 not complete), return empty array
463
- console.warn('Standards patterns table not found. Assuming Phase 1 not complete.');
463
+ // Database unavailable fall back to reading YAML files directly
464
+ console.warn('Standards patterns table not found. Falling back to file-based standards.');
465
+ return this.loadStandardsFromFiles(categories);
466
+ }
467
+ }
468
+
469
+ /**
470
+ * File-system fallback: load and filter standards from YAML files
471
+ * Used when database is unavailable (local dev, first run, DB down)
472
+ */
473
+ async loadStandardsFromFiles(categories) {
474
+ try {
475
+ const standardsPath = path.isAbsolute(this.standardsPath)
476
+ ? this.standardsPath
477
+ : path.join(this.workingDirectory, this.standardsPath);
478
+
479
+ const ingestion = new StandardsIngestion({ standardsPath });
480
+ const allPatterns = await ingestion.parseAllStandards();
481
+
482
+ // Filter by requested categories and enforced/validated maturity
483
+ const validMaturity = ['enforced', 'validated', 'recommended'];
484
+ const categorySet = new Set(categories);
485
+
486
+ return allPatterns
487
+ .filter(p => categorySet.has(p.category) && validMaturity.includes(p.maturity))
488
+ .sort((a, b) => {
489
+ const maturityOrder = { enforced: 1, validated: 2, recommended: 3 };
490
+ return (maturityOrder[a.maturity] || 3) - (maturityOrder[b.maturity] || 3);
491
+ });
492
+ } catch (fallbackError) {
493
+ console.error('File-based standards fallback failed:', fallbackError.message);
464
494
  return [];
465
495
  }
466
496
  }