@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 +2 -2
- package/dist/index.js +554 -8
- package/package.json +1 -1
- package/src/index-api.ts +2 -2
- package/src/index.ts +633 -10
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
} :
|
|
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.
|
|
838
|
-
updates.
|
|
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
|
-
|
|
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.
|
|
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
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
|
-
|
|
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
|
-
|
|
287
|
+
resolution_notes: args.resolution_notes,
|
|
288
288
|
});
|
|
289
289
|
break;
|
|
290
290
|
|