use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IssueCategory {
Style,
Security,
Performance,
Complexity,
Bug,
Deprecation,
Type,
Documentation,
BestPractice,
#[default]
General,
}
impl std::str::FromStr for IssueCategory {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"style" | "formatting" | "naming" => Ok(Self::Style),
"security" | "vulnerability" | "vuln" => Ok(Self::Security),
"performance" | "perf" | "speed" => Ok(Self::Performance),
"complexity" | "cyclomatic" | "cognitive" => Ok(Self::Complexity),
"bug" | "error" | "defect" => Ok(Self::Bug),
"deprecation" | "deprecated" => Ok(Self::Deprecation),
"type" | "typing" => Ok(Self::Type),
"documentation" | "doc" | "docs" => Ok(Self::Documentation),
"best-practice" | "bestpractice" | "practice" => Ok(Self::BestPractice),
_ => Ok(Self::General),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptTemplate {
pub name: String,
pub category: IssueCategory,
pub system_prompt: String,
pub user_prompt_template: String,
pub context_instructions: Option<String>,
}
impl PromptTemplate {
pub fn new(name: &str, category: IssueCategory, system: &str, template: &str) -> Self {
Self {
name: name.to_string(),
category,
system_prompt: system.to_string(),
user_prompt_template: template.to_string(),
context_instructions: None,
}
}
pub fn with_context_instructions(mut self, instructions: &str) -> Self {
self.context_instructions = Some(instructions.to_string());
self
}
}
pub struct PromptBuilder {
templates: HashMap<IssueCategory, PromptTemplate>,
default_template: PromptTemplate,
}
impl Default for PromptBuilder {
fn default() -> Self {
Self::new()
}
}
impl PromptBuilder {
pub fn new() -> Self {
let mut templates = HashMap::new();
templates.insert(
IssueCategory::Style,
PromptTemplate::new(
"style_fix",
IssueCategory::Style,
SYSTEM_PROMPT_STYLE,
USER_PROMPT_STYLE,
),
);
templates.insert(
IssueCategory::Security,
PromptTemplate::new(
"security_fix",
IssueCategory::Security,
SYSTEM_PROMPT_SECURITY,
USER_PROMPT_SECURITY,
),
);
templates.insert(
IssueCategory::Performance,
PromptTemplate::new(
"performance_fix",
IssueCategory::Performance,
SYSTEM_PROMPT_PERFORMANCE,
USER_PROMPT_PERFORMANCE,
),
);
templates.insert(
IssueCategory::Complexity,
PromptTemplate::new(
"complexity_fix",
IssueCategory::Complexity,
SYSTEM_PROMPT_COMPLEXITY,
USER_PROMPT_COMPLEXITY,
),
);
templates.insert(
IssueCategory::Bug,
PromptTemplate::new(
"bug_fix",
IssueCategory::Bug,
SYSTEM_PROMPT_BUG,
USER_PROMPT_BUG,
),
);
let default_template = PromptTemplate::new(
"general_fix",
IssueCategory::General,
SYSTEM_PROMPT_GENERAL,
USER_PROMPT_GENERAL,
);
Self {
templates,
default_template,
}
}
pub fn get_template(&self, category: IssueCategory) -> &PromptTemplate {
self.templates
.get(&category)
.unwrap_or(&self.default_template)
}
pub fn build_prompt(
&self,
category: IssueCategory,
variables: &PromptVariables,
) -> (String, String) {
let template = self.get_template(category);
let system = self.substitute_variables(&template.system_prompt, variables);
let user = self.substitute_variables(&template.user_prompt_template, variables);
(system, user)
}
fn substitute_variables(&self, template: &str, vars: &PromptVariables) -> String {
let mut result = template.to_string();
result = result.replace("{{language}}", &vars.language);
result = result.replace("{{file_path}}", &vars.file_path);
result = result.replace("{{line_number}}", &vars.line_number.to_string());
result = result.replace("{{issue_message}}", &vars.issue_message);
result = result.replace("{{rule_id}}", &vars.rule_id);
result = result.replace("{{code_context}}", &vars.code_context);
result = result.replace("{{issue_line}}", &vars.issue_line);
if let Some(ref imports) = vars.imports {
result = result.replace("{{imports}}", imports);
} else {
result = result.replace("{{imports}}", "");
}
if let Some(ref scope) = vars.scope {
result = result.replace("{{scope}}", scope);
} else {
result = result.replace("{{scope}}", "");
}
result
}
pub fn add_template(&mut self, template: PromptTemplate) {
self.templates.insert(template.category, template);
}
}
#[derive(Debug, Clone, Default)]
pub struct PromptVariables {
pub language: String,
pub file_path: String,
pub line_number: u32,
pub issue_message: String,
pub rule_id: String,
pub code_context: String,
pub issue_line: String,
pub imports: Option<String>,
pub scope: Option<String>,
}
impl PromptVariables {
pub fn new() -> Self {
Self::default()
}
pub fn with_language(mut self, lang: &str) -> Self {
self.language = lang.to_string();
self
}
pub fn with_file_path(mut self, path: &str) -> Self {
self.file_path = path.to_string();
self
}
pub fn with_line_number(mut self, line: u32) -> Self {
self.line_number = line;
self
}
pub fn with_issue_message(mut self, msg: &str) -> Self {
self.issue_message = msg.to_string();
self
}
pub fn with_rule_id(mut self, rule: &str) -> Self {
self.rule_id = rule.to_string();
self
}
pub fn with_code_context(mut self, context: &str) -> Self {
self.code_context = context.to_string();
self
}
pub fn with_issue_line(mut self, line: &str) -> Self {
self.issue_line = line.to_string();
self
}
pub fn with_imports(mut self, imports: &str) -> Self {
self.imports = Some(imports.to_string());
self
}
pub fn with_scope(mut self, scope: &str) -> Self {
self.scope = Some(scope.to_string());
self
}
}
const SYSTEM_PROMPT_GENERAL: &str = r#"You are an expert code reviewer and fix assistant. Your task is to analyze lint issues and provide precise, minimal fixes.
CRITICAL: Always provide your fix as a unified diff. This ensures accurate application of changes.
Response format - use unified diff:
```diff
@@ -LINE_NUM,COUNT +LINE_NUM,COUNT @@
context line (unchanged, starts with space)
-removed line (starts with minus)
+added line (starts with plus)
context line (unchanged, starts with space)
```
Guidelines:
1. Make MINIMAL changes - fix ONLY what the error message describes
2. Preserve original code style, formatting, and indentation
3. Include 1 line of context before and after the change
4. Do NOT include the entire file - only the changed section
5. LINE_NUM is the line number where the change starts
6. COUNT is the number of lines in that section
Common lint rules and their fixes:
- "Missing space after X" → Add a space AFTER the character X
- "Missing space before X" → Add a space BEFORE the character X
- "Extra space" → Remove the extra space
- "Line too long" → Break the line appropriately
- "Unused variable" → Remove or prefix with underscore
Example - fixing "unused variable x" on line 5:
```diff
@@ -4,3 +4,3 @@
let y = 10;
- let x = 5;
+ let _x = 5;
println!("{}", y);
```"#;
const SYSTEM_PROMPT_STYLE: &str = r#"You are an expert code formatter and style guide enforcer. Your task is to fix code style issues while preserving functionality.
CRITICAL: Always provide your fix as a unified diff. This ensures accurate application of changes.
Response format - use unified diff:
```diff
@@ -LINE_NUM,COUNT +LINE_NUM,COUNT @@
context line (unchanged, starts with space)
-removed line (starts with minus)
+added line (starts with plus)
context line (unchanged, starts with space)
```
Guidelines:
1. Follow language-specific style conventions
2. Make MINIMAL changes - fix ONLY what the error message describes
3. Preserve existing formatting patterns where not explicitly wrong
4. Do NOT change indentation unless the error specifically mentions indentation
5. Include 1 line of context before and after the change
6. Do NOT include the entire file
Common style rules and their fixes:
- "Missing space after X" → Add a space AFTER the character X
- "Missing space before X" → Add a space BEFORE the character X
- "Extra space after X" → Remove the extra space after X
- "Line too long" → Break the line appropriately
- "Trailing whitespace" → Remove spaces/tabs at the end of line"#;
const SYSTEM_PROMPT_SECURITY: &str = r#"You are a security expert. Your task is to fix security vulnerabilities in code while maintaining functionality.
CRITICAL: Always provide your fix as a unified diff:
```diff
@@ -LINE_NUM,COUNT +LINE_NUM,COUNT @@
context line (unchanged)
-removed line
+added line
```
Guidelines:
1. Apply security best practices
2. Use secure alternatives to vulnerable patterns
3. Add input validation where needed
4. Include 1-2 lines of context before and after
5. Do NOT include the entire file
Security note: Add brief explanation after the diff if needed"#;
const SYSTEM_PROMPT_PERFORMANCE: &str = r#"You are a performance optimization expert. Your task is to fix performance issues while maintaining code correctness.
CRITICAL: Always provide your fix as a unified diff:
```diff
@@ -LINE_NUM,COUNT +LINE_NUM,COUNT @@
context line (unchanged)
-removed line
+added line
```
Guidelines:
1. Optimize only what's flagged as a performance issue
2. Prefer clarity over micro-optimizations
3. Include 1-2 lines of context before and after
4. Do NOT include the entire file"#;
const SYSTEM_PROMPT_COMPLEXITY: &str = r#"You are a code simplification expert. Your task is to reduce code complexity while maintaining functionality.
CRITICAL: Always provide your fix as a unified diff:
```diff
@@ -LINE_NUM,COUNT +LINE_NUM,COUNT @@
context line (unchanged)
-removed line
+added line
```
Guidelines:
1. Simplify conditional logic or reduce nesting
2. Maintain the original behavior exactly
3. Include context lines before and after
4. Do NOT include the entire file"#;
const SYSTEM_PROMPT_BUG: &str = r#"You are a debugging expert. Your task is to fix potential bugs and error patterns in code.
CRITICAL: Always provide your fix as a unified diff:
```diff
@@ -LINE_NUM,COUNT +LINE_NUM,COUNT @@
context line (unchanged)
-removed line
+added line
```
Guidelines:
1. Fix the specific bug pattern identified
2. Consider edge cases
3. Include 1-2 lines of context before and after
4. Do NOT include the entire file
Bug fix note: Add brief explanation after the diff if needed"#;
const USER_PROMPT_GENERAL: &str = r#"Fix the following lint issue in {{language}} code:
File: {{file_path}}
Line: {{line_number}}
Issue: {{issue_message}}
Rule: {{rule_id}}
Code context:
```{{language}}
{{code_context}}
```
The issue is on this line (line {{line_number}}):
```{{language}}
{{issue_line}}
```
IMPORTANT:
- The error message "{{issue_message}}" tells you EXACTLY what to fix
- Fix ONLY what the error describes, do NOT change other lines
- Preserve original indentation and formatting
Provide your fix as a unified diff starting at line {{line_number}}:
```diff
@@ -{{line_number}},N +{{line_number}},M @@
context line before
-original problematic line
+fixed line
context line after
```"#;
const USER_PROMPT_STYLE: &str = r#"Fix the following code style issue in {{language}}:
File: {{file_path}}
Line: {{line_number}}
Style Issue: {{issue_message}}
Rule: {{rule_id}}
Code context:
```{{language}}
{{code_context}}
```
Problem line (line {{line_number}}):
```{{language}}
{{issue_line}}
```
IMPORTANT:
- "{{issue_message}}" tells you EXACTLY what to fix
- Make the MINIMAL change to fix ONLY this specific issue
- Do NOT change other lines or formatting
Provide your fix as a unified diff:
```diff
@@ -{{line_number}},1 +{{line_number}},1 @@
-{{issue_line}}
+fixed line here
```"#;
const USER_PROMPT_SECURITY: &str = r#"Fix the following security vulnerability in {{language}}:
File: {{file_path}}
Line: {{line_number}}
Security Issue: {{issue_message}}
Rule: {{rule_id}}
Vulnerable code:
```{{language}}
{{code_context}}
```
Problem line (line {{line_number}}):
```{{language}}
{{issue_line}}
```
Provide your fix as a unified diff:
```diff
@@ -{{line_number}},N +{{line_number}},M @@
-vulnerable code
+secure code
```"#;
const USER_PROMPT_PERFORMANCE: &str = r#"Optimize the following performance issue in {{language}}:
File: {{file_path}}
Line: {{line_number}}
Performance Issue: {{issue_message}}
Rule: {{rule_id}}
Code to optimize:
```{{language}}
{{code_context}}
```
Problem area (line {{line_number}}):
```{{language}}
{{issue_line}}
```
Provide your fix as a unified diff:
```diff
@@ -{{line_number}},N +{{line_number}},M @@
-slow code
+optimized code
```"#;
const USER_PROMPT_COMPLEXITY: &str = r#"Simplify the following complex code in {{language}}:
File: {{file_path}}
Line: {{line_number}}
Complexity Issue: {{issue_message}}
Rule: {{rule_id}}
Complex code:
```{{language}}
{{code_context}}
```
{{#if scope}}
Full function/method:
```{{language}}
{{scope}}
```
{{/if}}
Provide your fix as a unified diff:
```diff
@@ -{{line_number}},N +{{line_number}},M @@
-complex code
+simplified code
```"#;
const USER_PROMPT_BUG: &str = r#"Fix the following potential bug in {{language}}:
File: {{file_path}}
Line: {{line_number}}
Bug Pattern: {{issue_message}}
Rule: {{rule_id}}
Buggy code:
```{{language}}
{{code_context}}
```
Problem line (line {{line_number}}):
```{{language}}
{{issue_line}}
```
Provide your fix as a unified diff:
```diff
@@ -{{line_number}},N +{{line_number}},M @@
-buggy code
+fixed code
```"#;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_issue_category_parsing() {
assert_eq!(
"style".parse::<IssueCategory>().unwrap(),
IssueCategory::Style
);
assert_eq!(
"security".parse::<IssueCategory>().unwrap(),
IssueCategory::Security
);
assert_eq!(
"performance".parse::<IssueCategory>().unwrap(),
IssueCategory::Performance
);
assert_eq!(
"unknown".parse::<IssueCategory>().unwrap(),
IssueCategory::General
);
}
#[test]
fn test_prompt_builder() {
let builder = PromptBuilder::new();
let vars = PromptVariables::new()
.with_language("rust")
.with_file_path("src/main.rs")
.with_line_number(10)
.with_issue_message("unused variable")
.with_rule_id("W0001")
.with_code_context("let x = 5;")
.with_issue_line("let x = 5;");
let (system, user) = builder.build_prompt(IssueCategory::General, &vars);
assert!(system.contains("expert code reviewer"));
assert!(user.contains("rust"));
assert!(user.contains("src/main.rs"));
assert!(user.contains("unused variable"));
}
#[test]
fn test_prompt_template() {
let template =
PromptTemplate::new("test", IssueCategory::Style, "System prompt", "User prompt");
assert_eq!(template.name, "test");
assert_eq!(template.category, IssueCategory::Style);
}
#[test]
fn test_category_specific_templates() {
let builder = PromptBuilder::new();
let security = builder.get_template(IssueCategory::Security);
assert!(security.system_prompt.contains("security"));
let performance = builder.get_template(IssueCategory::Performance);
assert!(performance.system_prompt.contains("performance"));
}
}