Skip to content

Commit fa23662

Browse files
GeneAIclaude
authored andcommitted
feat: Add Redis integration with environment-aware key prefixing
- Add ioredis client with graceful fallback to in-memory storage - Implement environment-aware key prefixing (prod:/preview:/dev:) - Add debug wizard resolve and stats API routes - Update analyze route to use Redis for pattern correlation - Update SBAR wizard to use Redis for session storage - Add build:vercel script to skip mkdocs build on Vercel 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 8c19bc7 commit fa23662

File tree

7 files changed

+855
-9
lines changed

7 files changed

+855
-9
lines changed

website/app/api/debug-wizard/analyze/route.ts

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ import {
2828
validateFilesForTier,
2929
} from '@/lib/debug-wizard/tiers';
3030

31+
import {
32+
findSimilarPatterns,
33+
isRedisAvailable,
34+
type StoredBugPattern,
35+
} from '@/lib/redis';
36+
3137
// ============================================================================
3238
// Error Pattern Classifiers (mirrors Python wizard)
3339
// ============================================================================
@@ -219,17 +225,70 @@ function getFileExtension(filePath: string): string {
219225
return parts.length > 1 ? `.${parts[parts.length - 1]}` : 'unknown';
220226
}
221227

228+
/**
229+
* Convert stored Redis pattern to HistoricalMatch format
230+
*/
231+
function convertToHistoricalMatch(
232+
pattern: StoredBugPattern,
233+
errorType: ErrorType,
234+
fileExt: string
235+
): HistoricalMatch {
236+
const matchingFactors: string[] = [];
237+
let similarityScore = 0.5; // Base score
238+
239+
// Boost for same error type
240+
if (pattern.error_type === errorType) {
241+
similarityScore += 0.3;
242+
matchingFactors.push(`Same error type: ${pattern.error_type}`);
243+
}
244+
245+
// Boost for same file type
246+
if (pattern.file_type === fileExt) {
247+
similarityScore += 0.15;
248+
matchingFactors.push(`Same file type: ${fileExt}`);
249+
}
250+
251+
return {
252+
date: pattern.date,
253+
file: pattern.file_path,
254+
error_type: pattern.error_type as ErrorType,
255+
root_cause: pattern.root_cause,
256+
fix_applied: pattern.fix_applied,
257+
fix_code: pattern.fix_code,
258+
resolution_time_minutes: pattern.resolution_time_minutes,
259+
similarity_score: Math.min(similarityScore, 1.0),
260+
matching_factors: matchingFactors,
261+
};
262+
}
263+
222264
/**
223265
* Find matching historical patterns based on error
266+
* Uses Redis when available, falls back to demo patterns
224267
*/
225-
function findHistoricalMatches(
268+
async function findHistoricalMatches(
226269
errorType: ErrorType,
227270
filePath: string,
228271
maxMatches: number
229-
): HistoricalMatch[] {
272+
): Promise<HistoricalMatch[]> {
230273
const fileExt = getFileExtension(filePath);
231274

232-
// Filter and adjust similarity scores based on current error
275+
// Try Redis first
276+
if (isRedisAvailable()) {
277+
try {
278+
const redisPatterns = await findSimilarPatterns(errorType, fileExt, maxMatches);
279+
280+
if (redisPatterns.length > 0) {
281+
return redisPatterns
282+
.map(p => convertToHistoricalMatch(p, errorType, fileExt))
283+
.sort((a, b) => b.similarity_score - a.similarity_score)
284+
.slice(0, maxMatches);
285+
}
286+
} catch (error) {
287+
console.error('Redis pattern lookup failed, using demo patterns:', error);
288+
}
289+
}
290+
291+
// Fall back to demo patterns
233292
const matches = DEMO_HISTORICAL_PATTERNS
234293
.map((pattern) => {
235294
let adjustedScore = pattern.similarity_score;
@@ -502,7 +561,7 @@ export async function POST(request: Request): Promise<Response> {
502561
let recommendedFix: RecommendedFix | null = null;
503562

504563
if (correlate_with_history && limits.historicalCorrelationEnabled) {
505-
historicalMatches = findHistoricalMatches(
564+
historicalMatches = await findHistoricalMatches(
506565
errorType,
507566
file_path || '',
508567
limits.maxHistoricalMatches
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* Debug Wizard Resolve API Route
3+
*
4+
* Stores resolved bug patterns for future reference.
5+
* Patterns are stored in Redis for persistence across sessions.
6+
*/
7+
8+
export const dynamic = 'force-dynamic';
9+
10+
import { NextResponse } from 'next/server';
11+
import { storeBugPattern, type StoredBugPattern } from '@/lib/redis';
12+
13+
interface ResolveInput {
14+
bug_id: string;
15+
error_type: string;
16+
error_message: string;
17+
file_path: string;
18+
root_cause: string;
19+
fix_applied: string;
20+
fix_code?: string;
21+
resolution_time_minutes: number;
22+
user_id?: string;
23+
}
24+
25+
/**
26+
* Get file extension from path
27+
*/
28+
function getFileExtension(filePath: string): string {
29+
const parts = filePath.split('.');
30+
return parts.length > 1 ? `.${parts[parts.length - 1]}` : 'unknown';
31+
}
32+
33+
export async function POST(request: Request): Promise<Response> {
34+
try {
35+
const body: ResolveInput = await request.json();
36+
const {
37+
bug_id,
38+
error_type,
39+
error_message,
40+
file_path,
41+
root_cause,
42+
fix_applied,
43+
fix_code,
44+
resolution_time_minutes,
45+
user_id,
46+
} = body;
47+
48+
// Validate required fields
49+
if (!bug_id || !error_type || !error_message || !root_cause || !fix_applied) {
50+
return NextResponse.json(
51+
{
52+
success: false,
53+
error: 'Missing required fields: bug_id, error_type, error_message, root_cause, fix_applied',
54+
},
55+
{ status: 400 }
56+
);
57+
}
58+
59+
// Create pattern to store
60+
const pattern: StoredBugPattern = {
61+
id: bug_id,
62+
date: new Date().toISOString().slice(0, 10),
63+
error_type,
64+
error_message,
65+
file_path: file_path || 'unknown',
66+
file_type: file_path ? getFileExtension(file_path) : 'unknown',
67+
root_cause,
68+
fix_applied,
69+
fix_code: fix_code || null,
70+
resolution_time_minutes: resolution_time_minutes || 0,
71+
user_id,
72+
};
73+
74+
// Store pattern (Redis or in-memory fallback)
75+
const stored = await storeBugPattern(pattern);
76+
77+
if (!stored) {
78+
return NextResponse.json(
79+
{
80+
success: false,
81+
error: 'Failed to store pattern',
82+
},
83+
{ status: 500 }
84+
);
85+
}
86+
87+
return NextResponse.json({
88+
success: true,
89+
data: {
90+
pattern_id: bug_id,
91+
stored: true,
92+
message: 'Bug pattern stored successfully. This will help with future debugging!',
93+
},
94+
});
95+
} catch (error) {
96+
console.error('Debug wizard resolve error:', error);
97+
98+
return NextResponse.json(
99+
{
100+
success: false,
101+
error: 'Internal server error',
102+
},
103+
{ status: 500 }
104+
);
105+
}
106+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Debug Wizard Stats API Route
3+
*
4+
* Returns statistics about stored bug patterns.
5+
*/
6+
7+
export const dynamic = 'force-dynamic';
8+
9+
import { NextResponse } from 'next/server';
10+
import { getPatternStats, isRedisAvailable } from '@/lib/redis';
11+
12+
export async function GET(): Promise<Response> {
13+
try {
14+
const stats = await getPatternStats();
15+
16+
return NextResponse.json({
17+
success: true,
18+
data: {
19+
total_patterns: stats.total,
20+
patterns_by_type: stats.byType,
21+
storage_mode: stats.mode,
22+
redis_available: isRedisAvailable(),
23+
environment: stats.environment,
24+
key_prefix: stats.keyPrefix,
25+
},
26+
});
27+
} catch (error) {
28+
console.error('Debug wizard stats error:', error);
29+
30+
return NextResponse.json(
31+
{
32+
success: false,
33+
error: 'Failed to retrieve stats',
34+
},
35+
{ status: 500 }
36+
);
37+
}
38+
}

website/app/api/wizards/sbar/start/route.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import { NextResponse } from 'next/server';
2+
import { storeWizardSession, type WizardSession } from '@/lib/redis';
23

34
export async function POST() {
45
try {
56
const wizardId = `wizard_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
7+
const now = new Date();
8+
const expiresAt = new Date(now.getTime() + 60 * 60 * 1000); // 1 hour expiry
69

7-
const sessionData = {
10+
const sessionData: WizardSession = {
811
wizard_id: wizardId,
912
wizard_type: 'sbar',
1013
current_step: 1,
1114
total_steps: 4,
1215
collected_data: {},
13-
created_at: new Date().toISOString(),
14-
updated_at: new Date().toISOString(),
16+
created_at: now.toISOString(),
17+
updated_at: now.toISOString(),
18+
expires_at: expiresAt.toISOString(),
1519
};
1620

21+
// Store session in Redis (falls back to in-memory if unavailable)
22+
await storeWizardSession(sessionData);
23+
1724
return NextResponse.json({
1825
success: true,
1926
data: {

0 commit comments

Comments
 (0)