@bbearai/mcp-server 0.3.0 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index-api.js CHANGED
@@ -115,7 +115,7 @@ const tools = [
115
115
  enum: ['new', 'reviewed', 'in_progress', 'resolved', 'closed'],
116
116
  description: 'New status',
117
117
  },
118
- resolution: {
118
+ resolution_notes: {
119
119
  type: 'string',
120
120
  description: 'Resolution notes (optional)',
121
121
  },
@@ -267,7 +267,7 @@ async function handleTool(name, args) {
267
267
  case 'update_report_status':
268
268
  result = await apiRequest(`/reports/${args.report_id}`, 'PATCH', {
269
269
  status: args.status,
270
- resolution: args.resolution,
270
+ resolution_notes: args.resolution_notes,
271
271
  });
272
272
  break;
273
273
  case 'create_bug_report': {
package/dist/index.js CHANGED
@@ -127,7 +127,7 @@ const tools = [
127
127
  enum: ['new', 'triaging', 'confirmed', 'in_progress', 'fixed', 'resolved', 'verified', 'wont_fix', 'duplicate'],
128
128
  description: 'The new status for the report',
129
129
  },
130
- resolution: {
130
+ resolution_notes: {
131
131
  type: 'string',
132
132
  description: 'Optional resolution notes when marking as resolved',
133
133
  },
@@ -722,12 +722,165 @@ const tools = [
722
722
  },
723
723
  },
724
724
  },
725
+ // === SPRINT 5: QA INTELLIGENCE EXPANSION ===
726
+ {
727
+ name: 'get_qa_sessions',
728
+ description: 'Get QA session history showing exploratory testing sessions. Sessions capture findings that may or may not become formal bugs. Use this to understand tester exploration patterns and unstructured discoveries.',
729
+ inputSchema: {
730
+ type: 'object',
731
+ properties: {
732
+ status: {
733
+ type: 'string',
734
+ enum: ['active', 'completed', 'all'],
735
+ description: 'Filter by session status (default: all)',
736
+ },
737
+ tester_id: {
738
+ type: 'string',
739
+ description: 'Filter by specific tester UUID',
740
+ },
741
+ limit: {
742
+ type: 'number',
743
+ description: 'Maximum sessions to return (default: 20)',
744
+ },
745
+ include_findings: {
746
+ type: 'boolean',
747
+ description: 'Include session findings in response (default: true)',
748
+ },
749
+ },
750
+ },
751
+ },
752
+ {
753
+ name: 'get_qa_alerts',
754
+ description: 'Get active QA alerts including hot spots (routes with many bugs), coverage gaps (untested areas), and track gaps. Alerts are proactively generated to highlight areas needing attention.',
755
+ inputSchema: {
756
+ type: 'object',
757
+ properties: {
758
+ severity: {
759
+ type: 'string',
760
+ enum: ['critical', 'warning', 'info', 'all'],
761
+ description: 'Filter by alert severity (default: all)',
762
+ },
763
+ type: {
764
+ type: 'string',
765
+ enum: ['hot_spot', 'coverage_gap', 'track_gap', 'regression', 'all'],
766
+ description: 'Filter by alert type (default: all)',
767
+ },
768
+ status: {
769
+ type: 'string',
770
+ enum: ['active', 'acknowledged', 'all'],
771
+ description: 'Filter by status (default: active)',
772
+ },
773
+ refresh: {
774
+ type: 'boolean',
775
+ description: 'Refresh alerts by running detection (default: false)',
776
+ },
777
+ },
778
+ },
779
+ },
780
+ {
781
+ name: 'get_deployment_analysis',
782
+ description: 'Get deployment history with risk analysis. Shows what files/routes were affected by each deploy and the calculated risk score. Use this to understand deployment impact on QA.',
783
+ inputSchema: {
784
+ type: 'object',
785
+ properties: {
786
+ deployment_id: {
787
+ type: 'string',
788
+ description: 'Get specific deployment by ID',
789
+ },
790
+ environment: {
791
+ type: 'string',
792
+ enum: ['production', 'preview', 'staging', 'all'],
793
+ description: 'Filter by environment (default: all)',
794
+ },
795
+ limit: {
796
+ type: 'number',
797
+ description: 'Maximum deployments to return (default: 10)',
798
+ },
799
+ include_testing_priority: {
800
+ type: 'boolean',
801
+ description: 'Include testing priority recommendations (default: true)',
802
+ },
803
+ },
804
+ },
805
+ },
806
+ {
807
+ name: 'get_tester_recommendations',
808
+ description: 'Get recommended testers for a test case based on their expertise, track strengths, and past performance. Use this for smart test assignment.',
809
+ inputSchema: {
810
+ type: 'object',
811
+ properties: {
812
+ test_case_id: {
813
+ type: 'string',
814
+ description: 'Test case UUID to get recommendations for (required)',
815
+ },
816
+ limit: {
817
+ type: 'number',
818
+ description: 'Maximum recommendations to return (default: 5)',
819
+ },
820
+ },
821
+ required: ['test_case_id'],
822
+ },
823
+ },
824
+ {
825
+ name: 'analyze_commit_for_testing',
826
+ description: 'Analyze a commit to suggest what should be tested. Maps changed files to routes/features and checks bug history to provide prioritized testing recommendations.',
827
+ inputSchema: {
828
+ type: 'object',
829
+ properties: {
830
+ commit_sha: {
831
+ type: 'string',
832
+ description: 'Git commit SHA to analyze',
833
+ },
834
+ files_changed: {
835
+ type: 'array',
836
+ items: { type: 'string' },
837
+ description: 'List of files changed in the commit',
838
+ },
839
+ commit_message: {
840
+ type: 'string',
841
+ description: 'Commit message for context',
842
+ },
843
+ record_deployment: {
844
+ type: 'boolean',
845
+ description: 'Record this as a deployment for tracking (default: false)',
846
+ },
847
+ },
848
+ required: ['files_changed'],
849
+ },
850
+ },
851
+ {
852
+ name: 'get_testing_patterns',
853
+ description: 'Get common testing patterns from the curated pattern library. Patterns are based on public knowledge (OWASP, WCAG, framework docs) NOT customer data. Use this to suggest tests for specific feature types.',
854
+ inputSchema: {
855
+ type: 'object',
856
+ properties: {
857
+ feature_type: {
858
+ type: 'string',
859
+ enum: ['form', 'auth', 'payment', 'file_upload', 'search', 'navigation', 'settings', 'dashboard', 'checkout', 'media', 'async', 'error', 'rendering'],
860
+ description: 'Type of feature to get patterns for',
861
+ },
862
+ framework: {
863
+ type: 'string',
864
+ description: 'Framework to filter patterns (react, nextjs, vue, etc.)',
865
+ },
866
+ tracks: {
867
+ type: 'array',
868
+ items: { type: 'string' },
869
+ description: 'QA tracks to focus on (functional, security, accessibility, performance)',
870
+ },
871
+ search: {
872
+ type: 'string',
873
+ description: 'Search patterns by keyword instead of category',
874
+ },
875
+ },
876
+ },
877
+ },
725
878
  ];
726
879
  // Tool handlers
727
880
  async function listReports(args) {
728
881
  let query = supabase
729
882
  .from('reports')
730
- .select('id, report_type, severity, status, description, app_context, created_at, tester:testers(name, email)')
883
+ .select('id, report_type, severity, status, description, app_context, created_at, reporter_name, reporter_email, tester:testers(name, email)')
731
884
  .eq('project_id', PROJECT_ID)
732
885
  .order('created_at', { ascending: false })
733
886
  .limit(Math.min(args.limit || 10, 50));
@@ -749,7 +902,7 @@ async function listReports(args) {
749
902
  status: r.status,
750
903
  description: r.description,
751
904
  route: r.app_context?.currentRoute,
752
- reporter: r.tester?.name || 'Anonymous',
905
+ reporter: r.tester?.name || r.reporter_name || 'Anonymous',
753
906
  created_at: r.created_at,
754
907
  })),
755
908
  total: data?.length || 0,
@@ -784,7 +937,10 @@ async function getReport(args) {
784
937
  reporter: data.tester ? {
785
938
  name: data.tester.name,
786
939
  email: data.tester.email,
787
- } : null,
940
+ } : (data.reporter_name ? {
941
+ name: data.reporter_name,
942
+ email: data.reporter_email,
943
+ } : null),
788
944
  track: data.track ? {
789
945
  name: data.track.name,
790
946
  icon: data.track.icon,
@@ -834,8 +990,8 @@ async function updateReportStatus(args) {
834
990
  return { error: 'Invalid report_id format' };
835
991
  }
836
992
  const updates = { status: args.status };
837
- if (args.resolution) {
838
- updates.resolution = args.resolution;
993
+ if (args.resolution_notes) {
994
+ updates.resolution_notes = args.resolution_notes;
839
995
  }
840
996
  const { error } = await supabase
841
997
  .from('reports')
@@ -2134,6 +2290,353 @@ async function getQAHealth(args) {
2134
2290
  },
2135
2291
  };
2136
2292
  }
2293
+ // === SPRINT 5: NEW HANDLER FUNCTIONS ===
2294
+ async function getQASessions(args) {
2295
+ const status = args.status || 'all';
2296
+ const limit = Math.min(args.limit || 20, 50);
2297
+ const includeFindings = args.include_findings !== false;
2298
+ let query = supabase
2299
+ .from('qa_sessions')
2300
+ .select(`
2301
+ id, focus_area, track, platform, started_at, ended_at,
2302
+ notes, routes_covered, status, duration_minutes,
2303
+ findings_count, bugs_filed, created_at,
2304
+ tester:testers(id, name, email)
2305
+ `)
2306
+ .eq('project_id', PROJECT_ID)
2307
+ .order('started_at', { ascending: false })
2308
+ .limit(limit);
2309
+ if (status !== 'all') {
2310
+ query = query.eq('status', status);
2311
+ }
2312
+ if (args.tester_id && isValidUUID(args.tester_id)) {
2313
+ query = query.eq('tester_id', args.tester_id);
2314
+ }
2315
+ const { data: sessions, error } = await query;
2316
+ if (error) {
2317
+ return { error: error.message };
2318
+ }
2319
+ // Optionally load findings for each session
2320
+ let sessionsWithFindings = sessions || [];
2321
+ if (includeFindings && sessions && sessions.length > 0) {
2322
+ const sessionIds = sessions.map(s => s.id);
2323
+ const { data: findings } = await supabase
2324
+ .from('qa_findings')
2325
+ .select('id, session_id, type, severity, title, description, route, converted_to_bug_id, dismissed')
2326
+ .in('session_id', sessionIds);
2327
+ const findingsList = findings || [];
2328
+ const findingsBySession = findingsList.reduce((acc, f) => {
2329
+ acc[f.session_id] = acc[f.session_id] || [];
2330
+ acc[f.session_id].push(f);
2331
+ return acc;
2332
+ }, {});
2333
+ sessionsWithFindings = sessions.map(s => ({
2334
+ ...s,
2335
+ findings: findingsBySession[s.id] || [],
2336
+ }));
2337
+ }
2338
+ const summary = {
2339
+ total: sessionsWithFindings.length,
2340
+ active: sessionsWithFindings.filter(s => s.status === 'active').length,
2341
+ completed: sessionsWithFindings.filter(s => s.status === 'completed').length,
2342
+ totalFindings: sessionsWithFindings.reduce((sum, s) => sum + (s.findings_count || 0), 0),
2343
+ totalBugsFiled: sessionsWithFindings.reduce((sum, s) => sum + (s.bugs_filed || 0), 0),
2344
+ };
2345
+ return {
2346
+ sessions: sessionsWithFindings,
2347
+ summary,
2348
+ };
2349
+ }
2350
+ async function getQAAlerts(args) {
2351
+ const severity = args.severity || 'all';
2352
+ const type = args.type || 'all';
2353
+ const status = args.status || 'active';
2354
+ // Optionally refresh alerts
2355
+ if (args.refresh) {
2356
+ await supabase.rpc('detect_all_alerts', { p_project_id: PROJECT_ID });
2357
+ }
2358
+ let query = supabase
2359
+ .from('qa_alerts')
2360
+ .select('*')
2361
+ .eq('project_id', PROJECT_ID)
2362
+ .order('severity', { ascending: true }) // critical first
2363
+ .order('created_at', { ascending: false });
2364
+ if (severity !== 'all') {
2365
+ query = query.eq('severity', severity);
2366
+ }
2367
+ if (type !== 'all') {
2368
+ query = query.eq('type', type);
2369
+ }
2370
+ if (status !== 'all') {
2371
+ query = query.eq('status', status);
2372
+ }
2373
+ const { data: alerts, error } = await query;
2374
+ if (error) {
2375
+ return { error: error.message };
2376
+ }
2377
+ const summary = {
2378
+ total: alerts?.length || 0,
2379
+ critical: alerts?.filter(a => a.severity === 'critical').length || 0,
2380
+ warning: alerts?.filter(a => a.severity === 'warning').length || 0,
2381
+ info: alerts?.filter(a => a.severity === 'info').length || 0,
2382
+ byType: {
2383
+ hot_spot: alerts?.filter(a => a.type === 'hot_spot').length || 0,
2384
+ coverage_gap: alerts?.filter(a => a.type === 'coverage_gap').length || 0,
2385
+ track_gap: alerts?.filter(a => a.type === 'track_gap').length || 0,
2386
+ },
2387
+ };
2388
+ return {
2389
+ alerts: alerts?.map(a => ({
2390
+ id: a.id,
2391
+ type: a.type,
2392
+ severity: a.severity,
2393
+ title: a.title,
2394
+ description: a.description,
2395
+ route: a.trigger_route,
2396
+ track: a.trigger_track,
2397
+ recommendation: a.recommendation,
2398
+ action_type: a.action_type,
2399
+ status: a.status,
2400
+ created_at: a.created_at,
2401
+ })),
2402
+ summary,
2403
+ };
2404
+ }
2405
+ async function getDeploymentAnalysis(args) {
2406
+ const limit = Math.min(args.limit || 10, 50);
2407
+ const includeTestingPriority = args.include_testing_priority !== false;
2408
+ if (args.deployment_id && isValidUUID(args.deployment_id)) {
2409
+ // Get specific deployment
2410
+ const { data: deployment, error } = await supabase
2411
+ .from('deployments')
2412
+ .select('*')
2413
+ .eq('id', args.deployment_id)
2414
+ .eq('project_id', PROJECT_ID)
2415
+ .single();
2416
+ if (error) {
2417
+ return { error: error.message };
2418
+ }
2419
+ return { deployment };
2420
+ }
2421
+ // Get deployment history
2422
+ let query = supabase
2423
+ .from('deployments')
2424
+ .select('*')
2425
+ .eq('project_id', PROJECT_ID)
2426
+ .order('deployed_at', { ascending: false })
2427
+ .limit(limit);
2428
+ if (args.environment && args.environment !== 'all') {
2429
+ query = query.eq('environment', args.environment);
2430
+ }
2431
+ const { data: deployments, error } = await query;
2432
+ if (error) {
2433
+ return { error: error.message };
2434
+ }
2435
+ const summary = {
2436
+ total: deployments?.length || 0,
2437
+ avgRiskScore: deployments?.length
2438
+ ? Math.round(deployments.reduce((sum, d) => sum + (d.risk_score || 0), 0) / deployments.length)
2439
+ : 0,
2440
+ highRisk: deployments?.filter(d => (d.risk_score || 0) >= 70).length || 0,
2441
+ verified: deployments?.filter(d => d.verified_at).length || 0,
2442
+ };
2443
+ return {
2444
+ deployments: deployments?.map(d => ({
2445
+ id: d.id,
2446
+ environment: d.environment,
2447
+ commit_sha: d.commit_sha,
2448
+ commit_message: d.commit_message,
2449
+ branch: d.branch,
2450
+ deployed_at: d.deployed_at,
2451
+ risk_score: d.risk_score,
2452
+ routes_affected: d.routes_affected,
2453
+ testing_priority: includeTestingPriority ? d.testing_priority : undefined,
2454
+ verified: !!d.verified_at,
2455
+ })),
2456
+ summary,
2457
+ };
2458
+ }
2459
+ async function getTesterRecommendations(args) {
2460
+ if (!isValidUUID(args.test_case_id)) {
2461
+ return { error: 'Invalid test_case_id format' };
2462
+ }
2463
+ const limit = Math.min(args.limit || 5, 10);
2464
+ const { data: recommendations, error } = await supabase.rpc('get_tester_recommendations', {
2465
+ p_test_case_id: args.test_case_id,
2466
+ p_limit: limit,
2467
+ });
2468
+ if (error) {
2469
+ return { error: error.message };
2470
+ }
2471
+ // Get test case info for context
2472
+ const { data: testCase } = await supabase
2473
+ .from('test_cases')
2474
+ .select('test_key, title, track:qa_tracks(name)')
2475
+ .eq('id', args.test_case_id)
2476
+ .single();
2477
+ return {
2478
+ test_case: testCase ? {
2479
+ id: args.test_case_id,
2480
+ test_key: testCase.test_key,
2481
+ title: testCase.title,
2482
+ track: testCase.track?.name,
2483
+ } : null,
2484
+ recommendations: recommendations?.map((r) => ({
2485
+ tester_id: r.tester_id,
2486
+ name: r.tester_name,
2487
+ email: r.tester_email,
2488
+ match_score: r.match_score,
2489
+ reasons: r.match_reasons,
2490
+ })) || [],
2491
+ };
2492
+ }
2493
+ async function analyzeCommitForTesting(args) {
2494
+ const filesChanged = args.files_changed || [];
2495
+ // Map files to routes using file_route_mapping
2496
+ const { data: mappings } = await supabase
2497
+ .from('file_route_mapping')
2498
+ .select('file_pattern, route, feature, confidence')
2499
+ .eq('project_id', PROJECT_ID);
2500
+ const affectedRoutes = [];
2501
+ for (const mapping of mappings || []) {
2502
+ const matchedFiles = filesChanged.filter(file => {
2503
+ const pattern = mapping.file_pattern
2504
+ .replace(/\*\*/g, '.*')
2505
+ .replace(/\*/g, '[^/]*');
2506
+ return new RegExp(pattern).test(file);
2507
+ });
2508
+ if (matchedFiles.length > 0) {
2509
+ affectedRoutes.push({
2510
+ route: mapping.route,
2511
+ feature: mapping.feature || undefined,
2512
+ confidence: mapping.confidence,
2513
+ matched_files: matchedFiles,
2514
+ });
2515
+ }
2516
+ }
2517
+ // Get bug history for affected routes
2518
+ const routes = affectedRoutes.map(r => r.route);
2519
+ let bugHistory = [];
2520
+ if (routes.length > 0) {
2521
+ const { data: bugs } = await supabase
2522
+ .from('reports')
2523
+ .select('id, severity, description, route, created_at')
2524
+ .eq('project_id', PROJECT_ID)
2525
+ .eq('report_type', 'bug')
2526
+ .in('route', routes)
2527
+ .gte('created_at', new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString());
2528
+ bugHistory = bugs || [];
2529
+ }
2530
+ // Calculate risk score
2531
+ let riskScore = 0;
2532
+ riskScore += Math.min(filesChanged.length * 2, 20); // File count
2533
+ riskScore += Math.min(affectedRoutes.length * 5, 30); // Route count
2534
+ riskScore += Math.min(bugHistory.length * 10, 50); // Bug history
2535
+ // Generate testing recommendations
2536
+ const recommendations = affectedRoutes.map(r => {
2537
+ const routeBugs = bugHistory.filter(b => b.route === r.route);
2538
+ const priority = routeBugs.length >= 3 ? 'critical' :
2539
+ routeBugs.length >= 1 ? 'high' : 'medium';
2540
+ return {
2541
+ route: r.route,
2542
+ feature: r.feature,
2543
+ priority,
2544
+ reason: routeBugs.length > 0
2545
+ ? `${routeBugs.length} bug(s) in last 30 days`
2546
+ : 'Code changed in this area',
2547
+ recent_bugs: routeBugs.length,
2548
+ };
2549
+ }).sort((a, b) => {
2550
+ const order = { critical: 0, high: 1, medium: 2 };
2551
+ return order[a.priority] - order[b.priority];
2552
+ });
2553
+ // Optionally record as deployment
2554
+ if (args.record_deployment) {
2555
+ await supabase.rpc('record_deployment', {
2556
+ p_project_id: PROJECT_ID,
2557
+ p_environment: 'production',
2558
+ p_commit_sha: args.commit_sha || null,
2559
+ p_commit_message: args.commit_message || null,
2560
+ p_files_changed: filesChanged,
2561
+ p_webhook_source: 'mcp',
2562
+ });
2563
+ }
2564
+ return {
2565
+ commit_sha: args.commit_sha,
2566
+ files_analyzed: filesChanged.length,
2567
+ risk_score: Math.min(riskScore, 100),
2568
+ affected_routes: affectedRoutes,
2569
+ bug_history_summary: {
2570
+ total: bugHistory.length,
2571
+ critical: bugHistory.filter(b => b.severity === 'critical').length,
2572
+ high: bugHistory.filter(b => b.severity === 'high').length,
2573
+ },
2574
+ testing_recommendations: recommendations,
2575
+ deployment_recorded: args.record_deployment || false,
2576
+ };
2577
+ }
2578
+ async function getTestingPatterns(args) {
2579
+ let patterns = [];
2580
+ if (args.search) {
2581
+ // Search patterns by keyword
2582
+ const { data, error } = await supabase.rpc('search_patterns', {
2583
+ p_query: args.search,
2584
+ p_limit: 20,
2585
+ });
2586
+ if (!error)
2587
+ patterns = data || [];
2588
+ }
2589
+ else if (args.feature_type) {
2590
+ // Get patterns for feature type
2591
+ const { data, error } = await supabase.rpc('get_patterns_for_feature', {
2592
+ p_category: args.feature_type,
2593
+ p_framework: args.framework || null,
2594
+ p_tracks: args.tracks || null,
2595
+ });
2596
+ if (!error)
2597
+ patterns = data || [];
2598
+ }
2599
+ else {
2600
+ // Get all patterns (limited)
2601
+ const { data, error } = await supabase
2602
+ .from('qa_patterns')
2603
+ .select('*')
2604
+ .eq('is_active', true)
2605
+ .order('severity')
2606
+ .limit(30);
2607
+ if (!error)
2608
+ patterns = data || [];
2609
+ }
2610
+ const summary = {
2611
+ total: patterns.length,
2612
+ by_severity: {
2613
+ critical: patterns.filter(p => p.severity === 'critical').length,
2614
+ high: patterns.filter(p => p.severity === 'high').length,
2615
+ medium: patterns.filter(p => p.severity === 'medium').length,
2616
+ low: patterns.filter(p => p.severity === 'low').length,
2617
+ },
2618
+ by_track: patterns.reduce((acc, p) => {
2619
+ acc[p.track] = (acc[p.track] || 0) + 1;
2620
+ return acc;
2621
+ }, {}),
2622
+ };
2623
+ return {
2624
+ patterns: patterns.map(p => ({
2625
+ title: p.title,
2626
+ category: p.category,
2627
+ track: p.track,
2628
+ severity: p.severity,
2629
+ description: p.description,
2630
+ why_it_happens: p.why_it_happens,
2631
+ suggested_tests: p.suggested_tests,
2632
+ common_fix: p.common_fix,
2633
+ source: p.source,
2634
+ frameworks: p.frameworks,
2635
+ })),
2636
+ summary,
2637
+ note: 'Patterns are from public knowledge (OWASP, WCAG, framework docs), not customer data.',
2638
+ };
2639
+ }
2137
2640
  async function analyzeChangesForTests(args) {
2138
2641
  // Get existing tests to check coverage
2139
2642
  const { data: existingTests } = await supabase
@@ -2496,6 +2999,27 @@ async function createBugReport(args) {
2496
2999
  if (args.suggested_fix) {
2497
3000
  codeContext.suggested_fix = args.suggested_fix;
2498
3001
  }
3002
+ // Find a reporter_id: try to get the project owner or first tester
3003
+ let reporterId = null;
3004
+ const { data: project } = await supabase
3005
+ .from('projects')
3006
+ .select('owner_id')
3007
+ .eq('id', PROJECT_ID)
3008
+ .single();
3009
+ if (project?.owner_id) {
3010
+ reporterId = project.owner_id;
3011
+ }
3012
+ else {
3013
+ // Fallback: use the first tester for this project
3014
+ const { data: testers } = await supabase
3015
+ .from('testers')
3016
+ .select('id')
3017
+ .eq('project_id', PROJECT_ID)
3018
+ .limit(1);
3019
+ if (testers && testers.length > 0) {
3020
+ reporterId = testers[0].id;
3021
+ }
3022
+ }
2499
3023
  const report = {
2500
3024
  project_id: PROJECT_ID,
2501
3025
  report_type: 'bug',
@@ -2514,6 +3038,9 @@ async function createBugReport(args) {
2514
3038
  },
2515
3039
  code_context: codeContext,
2516
3040
  };
3041
+ if (reporterId) {
3042
+ report.reporter_id = reporterId;
3043
+ }
2517
3044
  const { data, error } = await supabase
2518
3045
  .from('reports')
2519
3046
  .insert(report)
@@ -2633,7 +3160,7 @@ async function markFixedWithCommit(args) {
2633
3160
  const updates = {
2634
3161
  status: 'resolved',
2635
3162
  resolved_at: new Date().toISOString(),
2636
- resolution: args.resolution_notes || `Fixed in commit ${args.commit_sha.slice(0, 7)}`,
3163
+ resolution_notes: args.resolution_notes || `Fixed in commit ${args.commit_sha.slice(0, 7)}`,
2637
3164
  code_context: {
2638
3165
  ...existingContext,
2639
3166
  fix: {
@@ -2863,7 +3390,7 @@ async function createRegressionTest(args) {
2863
3390
  {
2864
3391
  stepNumber: 3,
2865
3392
  action: 'Verify the fix is working',
2866
- expectedResult: report.resolution || 'Feature works as expected',
3393
+ expectedResult: report.resolution_notes || 'Feature works as expected',
2867
3394
  },
2868
3395
  ],
2869
3396
  expected_result: `The bug "${report.title}" should not recur`,
@@ -3491,6 +4018,25 @@ async function main() {
3491
4018
  case 'complete_fix_request':
3492
4019
  result = await completeFixRequest(args);
3493
4020
  break;
4021
+ // === SPRINT 5: NEW MCP TOOLS ===
4022
+ case 'get_qa_sessions':
4023
+ result = await getQASessions(args);
4024
+ break;
4025
+ case 'get_qa_alerts':
4026
+ result = await getQAAlerts(args);
4027
+ break;
4028
+ case 'get_deployment_analysis':
4029
+ result = await getDeploymentAnalysis(args);
4030
+ break;
4031
+ case 'get_tester_recommendations':
4032
+ result = await getTesterRecommendations(args);
4033
+ break;
4034
+ case 'analyze_commit_for_testing':
4035
+ result = await analyzeCommitForTesting(args);
4036
+ break;
4037
+ case 'get_testing_patterns':
4038
+ result = await getTestingPatterns(args);
4039
+ break;
3494
4040
  default:
3495
4041
  return {
3496
4042
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.3.4",
4
4
  "description": "MCP server for BugBear - allows Claude Code to query bug reports",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/index-api.ts CHANGED
@@ -129,7 +129,7 @@ const tools = [
129
129
  enum: ['new', 'reviewed', 'in_progress', 'resolved', 'closed'],
130
130
  description: 'New status',
131
131
  },
132
- resolution: {
132
+ resolution_notes: {
133
133
  type: 'string',
134
134
  description: 'Resolution notes (optional)',
135
135
  },
@@ -284,7 +284,7 @@ async function handleTool(
284
284
  case 'update_report_status':
285
285
  result = await apiRequest(`/reports/${args.report_id}`, 'PATCH', {
286
286
  status: args.status,
287
- resolution: args.resolution,
287
+ resolution_notes: args.resolution_notes,
288
288
  });
289
289
  break;
290
290