Skip to content

Commit aaafd9d

Browse files
committed
Enhance AI fix suggestion mechanism with LLM validation and source code context
1 parent 002a9b1 commit aaafd9d

9 files changed

Lines changed: 116 additions & 47 deletions

File tree

src/main/java/org/springforge/qualityassurance/actions/AnalyzeQualityAction.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,18 @@ class AnalyzeQualityAction : AnAction("Analyze Code Quality") {
8080
println("🟩 Initial report shown — overall: ${analysisResult.overall_display}, violations: ${analysisResult.total_violations}")
8181

8282
// ── STEP 5: Call Gemini for AI fix suggestions ────────────
83+
// If LLM validation was performed, fix_suggestions are already inline —
84+
// skip the separate /generate-fixes call.
8385
var aiFixCount = 0
84-
if (analysisResult.anti_patterns.isNotEmpty()) {
86+
if (analysisResult.anti_patterns.isNotEmpty() && !analysisResult.llm_enhanced) {
8587
indicator.text = "🤖 Generating AI fix suggestions (Gemini)..."
8688
indicator.fraction = 0.80
8789

8890
try {
89-
val fixResult = MLServiceClient.generateProjectFixes(analysisResult)
91+
val sourceMap = fileFeatures.associate { it.file_name to (it.source_code ?: "") }
92+
.filterValues { it.isNotBlank() }
93+
.ifEmpty { null }
94+
val fixResult = MLServiceClient.generateProjectFixes(analysisResult, sourceMap)
9095
aiFixCount = fixResult.total_fixes
9196
indicator.fraction = 0.95
9297

@@ -107,6 +112,9 @@ class AnalyzeQualityAction : AnAction("Analyze Code Quality") {
107112
)
108113
}
109114
}
115+
} else if (analysisResult.llm_enhanced) {
116+
aiFixCount = analysisResult.fix_suggestions.size
117+
println("🟩 LLM-enhanced: ${analysisResult.fix_suggestions.size} inline fixes, ${analysisResult.false_positives_filtered} false positives filtered")
110118
}
111119

112120
indicator.fraction = 1.0

src/main/java/org/springforge/qualityassurance/analysis/PsiFeatureExtractor.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,8 @@ object PsiFeatureExtractor {
435435
has_transaction = hasTransaction,
436436
violates_layer_separation = violatesLayer,
437437
uses_new_keyword = usesNewKeyword,
438-
has_broad_catch = hasBroadCatch
438+
has_broad_catch = hasBroadCatch,
439+
source_code = psiFile.text
439440
)
440441
}
441442

src/main/java/org/springforge/qualityassurance/model/AntiPatternDetail.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@ data class AntiPatternDetail(
88
val confidence: Double,
99
val files: List<String>,
1010
val description: String,
11-
val recommendation: String
11+
val recommendation: String,
12+
// v3: LLM validation fields
13+
val llm_validated: Boolean = false,
14+
val llm_description: String = "",
15+
val fix_suggestion: Map<String, Any>? = null
1216
)

src/main/java/org/springforge/qualityassurance/model/CombinedAnalysisResult.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,9 @@ data class CombinedAnalysisResult(
1717
val files_with_violations: Int = 0,
1818
val projected_score_after_fixes: Double = 0.0,
1919
val quality_summary: String = "",
20-
val violation_summary: String = ""
20+
val violation_summary: String = "",
21+
// v3: LLM validation summary
22+
val llm_enhanced: Boolean = false,
23+
val false_positives_filtered: Int = 0,
24+
val fix_suggestions: List<FixSuggestion> = emptyList()
2125
)

src/main/java/org/springforge/qualityassurance/model/FileFeatureModel.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,10 @@ data class FileFeatureModel(
5050

5151
// v2: new anti-pattern detection flags
5252
var uses_new_keyword : Boolean = false, // tight_coupling_new_keyword
53-
var has_broad_catch : Boolean = false // broad_catch
53+
var has_broad_catch : Boolean = false, // broad_catch
54+
55+
// v3: source code for LLM validation — when provided, Gemini validates
56+
// ML predictions against real code, filters false positives, and generates
57+
// context-aware fixes. When null, ML-only behavior is preserved.
58+
var source_code : String? = null
5459
)

src/main/java/org/springforge/qualityassurance/model/FixSuggestion.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,7 @@ data class SingleFixRequest(
4040
/** Request body for POST /generate-fixes (full project batch). */
4141
data class FixRequest(
4242
val anti_patterns : List<AntiPatternDetail> = emptyList(),
43-
val architecture_pattern : String = "layered"
43+
val architecture_pattern : String = "layered",
44+
// v3: optional source code map for context-aware fix generation
45+
val file_sources : Map<String, String>? = null
4446
)

src/main/java/org/springforge/qualityassurance/network/MLServiceClient.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit
2424
*/
2525
object MLServiceClient {
2626

27-
private const val BASE_URL = "https://api.springforge.dev/quality"
27+
private const val BASE_URL = "http://127.0.0.1:8000"
2828

2929
private val client = OkHttpClient.Builder()
3030
.connectTimeout(30, TimeUnit.SECONDS)
@@ -64,11 +64,17 @@ object MLServiceClient {
6464
/**
6565
* Calls POST /generate-fixes to get batch AI fix suggestions for all
6666
* anti-patterns detected by [analyzeProjectFull].
67+
*
68+
* @param fileSources optional map of file_name → source_code for context-aware fixes
6769
*/
68-
fun generateProjectFixes(analysisResult: CombinedAnalysisResult): ProjectFixResult {
70+
fun generateProjectFixes(
71+
analysisResult: CombinedAnalysisResult,
72+
fileSources: Map<String, String>? = null
73+
): ProjectFixResult {
6974
val request = FixRequest(
7075
anti_patterns = analysisResult.anti_patterns,
71-
architecture_pattern = normaliseArchitecture(analysisResult.architecture_pattern)
76+
architecture_pattern = normaliseArchitecture(analysisResult.architecture_pattern),
77+
file_sources = fileSources
7278
)
7379
val json = JsonUtil.toJson(request)
7480
println("🤖 Calling Gemini for ${analysisResult.anti_patterns.size} fix suggestions…")

src/main/java/org/springforge/qualityassurance/toolwindow/QualityToolWindowPanel.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,14 @@ class QualityToolWindowPanel : JPanel() {
8787
sb.ln("Total Issues Found : ${r.total_violations} violations across ${r.files_with_violations} files")
8888
when {
8989
fixes != null -> sb.ln("AI Fix Suggestions : ${fixes.total_fixes} (powered by Gemini 🤖)")
90+
r.fix_suggestions.isNotEmpty() -> sb.ln("AI Fix Suggestions : ${r.fix_suggestions.size} (LLM-validated, inline 🤖)")
9091
geminiWarning != null -> sb.ln("AI Fix Suggestions : ⚠️ $geminiWarning")
9192
r.total_violations > 0 -> sb.ln("AI Fix Suggestions : ⏳ Loading Gemini suggestions...")
9293
else -> sb.ln("AI Fix Suggestions : N/A (no violations)")
9394
}
95+
if (r.llm_enhanced) {
96+
sb.ln("LLM Validation : ✅ Gemini-validated (${r.false_positives_filtered} false positives filtered)")
97+
}
9498
sb.ln()
9599

96100
// ── QUALITY SCORE DASHBOARD ───────────────────────────────────────────
@@ -130,8 +134,10 @@ class QualityToolWindowPanel : JPanel() {
130134
sb.ln(" Your code follows ${r.architecture_pattern} best practices. 🎉")
131135
} else {
132136
// Build a lookup map: anti_pattern_type → FixSuggestion
137+
// Prefer inline fix_suggestions (from LLM-enhanced response), fall back to separate fixes call
133138
val fixMap: Map<String, FixSuggestion> =
134-
fixes?.suggestions?.associateBy { it.anti_pattern } ?: emptyMap()
139+
if (r.fix_suggestions.isNotEmpty()) r.fix_suggestions.associateBy { it.anti_pattern }
140+
else fixes?.suggestions?.associateBy { it.anti_pattern } ?: emptyMap()
135141

136142
val critical = r.anti_patterns.filter { it.severity == "CRITICAL" }
137143
val high = r.anti_patterns.filter { it.severity == "HIGH" }
@@ -335,6 +341,9 @@ class QualityToolWindowPanel : JPanel() {
335341
sb.ln("│ 📍 Affected Layer : ${ap.affected_layer}")
336342
sb.ln("│ 🎯 Confidence : ${(ap.confidence * 100).roundToInt()}%")
337343
sb.ln("│ 📉 Severity : ${ap.severity}")
344+
if (ap.llm_validated) {
345+
sb.ln("│ ✅ LLM Validated : Confirmed by Gemini")
346+
}
338347
if (fix != null && fix.impact_points != 0) {
339348
sb.ln("│ 📉 Impact on Quality : ${fix.impact_points} points")
340349
}
@@ -346,8 +355,10 @@ class QualityToolWindowPanel : JPanel() {
346355
if (ap.files.size > 5) sb.ln("│ ... and ${ap.files.size - 5} more")
347356
sb.ln("")
348357

349-
// Problem
350-
val problemText = fix?.problem?.takeIf { it.isNotBlank() } ?: ap.description
358+
// Problem — prefer LLM description (references actual code), then fix problem, then ap.description
359+
val problemText = ap.llm_description.takeIf { it.isNotBlank() }
360+
?: fix?.problem?.takeIf { it.isNotBlank() }
361+
?: ap.description
351362
sb.ln("│ 📖 Problem:")
352363
sb.ln("$problemText")
353364
sb.ln("")

0 commit comments

Comments
 (0)