Skip to main content

drep/analysis/
prompt.rs

1//! The code-quality system prompt for one language.
2//!
3//! The prompt is the bridge between the deterministic layer (the configured
4//! linters and formatters) and the semantic layer (the LLM): it tells the
5//! model what drep has already done and what is left for it to do. The body
6//! is identical for every language; only `display_name` and `conventions`
7//! vary, so the JSON shape the model is asked to return is never language-
8//! dependent and the parser in [`crate::analysis::code_quality`] stays total.
9//!
10//! Two instructions are load-bearing and must survive untouched:
11//!
12//! - The "do not report anything a formatter or linter would catch" line.
13//!   The deterministic layer already produces those findings, and without
14//!   this guard the model floods every file with lint noise that the gate
15//!   then has to deduplicate against the tooling's own output.
16//! - The schema block. A model that emits a different shape is, from the
17//!   parser's point of view, an unanalyzed file — the parser does not
18//!   attempt to recover a foreign schema.
19//!
20//! 2.x advertises two facts the Python did not need to: every line in the
21//! payload carries a real file line number in the gutter, and a finding must
22//! not be reported on a line that has no number. Together with
23//! [`crate::analysis::payload::Payload::valid_lines`] they are the line-
24//! number provenance the parser relies on to drop findings that point at
25//! code the model was never shown.
26
27use crate::analysis::findings::LlmSeverity;
28use crate::analysis::response_contract::{
29    CATEGORY, CODE_SNIPPET, COMPILE_FAILURE, ISSUES, LINE, MESSAGE, SEVERITY, SUGGESTION, SUMMARY,
30};
31use crate::languages::spec::LanguageSupport;
32
33/// Build the code-quality system prompt for one language.
34///
35/// The whole body is one template: the categories, the JSON schema, and the
36/// "do not duplicate the linter" instruction are fixed across languages, so
37/// the model is always answering the same question in the same shape. Only
38/// `display_name` and the optional `conventions` block move.
39///
40/// Pass `&LanguageSupport` rather than `&str` so the registry key
41/// (`"rust"`) cannot be substituted for the model-facing name (`"Rust"`).
42pub fn build_analysis_prompt(language: &LanguageSupport) -> String {
43    let conventions = conventions_block(language);
44    let display_name = language.display_name;
45    // Rendered from the same review vocabulary as the strict output schema.
46    // The parser accepts the wider legacy vocabulary so an old cache entry or
47    // unconstrained provider response cannot make a whole file malformed.
48    let severities = LlmSeverity::review_alternation();
49    let issues = ISSUES;
50    let summary = SUMMARY;
51    let line = LINE;
52    let severity = SEVERITY;
53    let category = CATEGORY;
54    let message = MESSAGE;
55    let suggestion = SUGGESTION;
56    let code_snippet = CODE_SNIPPET;
57    let compile_failure = COMPILE_FAILURE;
58    // The template is shaped so the conventions block, when empty, leaves
59    // no stray heading and no doubled blank line. The newline after the
60    // placeholder is the only one that exists in the template, so an
61    // empty conventions block is followed directly by `For each issue
62    // found`.
63    format!(
64        "You are an expert {display_name} code reviewer.\n\
65         Review the following code as a merge gate. Report only concrete issues\n\
66         that are worth fixing before merge:\n\
67         \n\
68         1. **Bugs & Logic Errors**: Incorrect logic, reachable crashes, data\n\
69            loss, broken contracts, type errors\n\
70         2. **Security Issues**: Injection, path traversal, unsafe deserialization,\n\
71            hardcoded secrets, weak cryptography\n\
72         3. **Reliability & Maintainability Defects**: Resource leaks, races,\n\
73            inconsistent state, or a design defect with a concrete failure mode\n\
74         4. **Performance Defects**: Material algorithmic or resource problems on\n\
75            a plausible execution path\n\
76         \n\
77         {conventions}\
78         For each issue found, provide:\n\
79         - The exact gutter line number of the affected code\n\
80         - Severity: critical (security vulnerabilities, data loss), high (bugs,\n\
81           crashes, serious issues), medium (material but non-critical defects).\n\
82           Low and info suggestions are outside this review and must not be emitted\n\
83         - Category: bug, security, performance, maintainability\n\
84         - Clear message explaining the issue\n\
85         - Specific, actionable suggestion for fixing it\n\
86         - The problematic code snippet\n\
87         - Whether the finding explicitly claims the code cannot compile\n\
88         \n\
89         **Important instructions:**\n\
90         - Only report a finding when it is concrete and reachable from the code\n\
91           shown, with a plausible execution path and a material consequence\n\
92         - This is not an exhaustive hardening exercise. Do not report optional hardening,\n\
93           extreme edge cases without a plausible execution path, nits, subjective\n\
94           preferences, cleanup, or refactoring opportunities\n\
95         - Do not report missing tests or documentation unless their absence creates\n\
96           a concrete product or API defect in the shown change\n\
97         - Prefer no finding over a speculative or marginal finding\n\
98         - Provide actionable suggestions, not vague advice\n\
99         - Focus on correctness, security, reliability, and material performance\n\
100         - The input is a line-numbered excerpt. Report the finding's `line`\n\
101           as the number shown in the gutter, never an offset into the\n\
102           excerpt. The excerpt itself states which lines are in scope.\n\
103         - Do not report subjective style issues, and do not report anything a\n\
104           formatter or linter would catch: those run separately and deterministically\n\
105         \n\
106         Return your analysis as valid JSON matching this exact schema:\n\
107         {{\n\
108           \"{issues}\": [\n\
109             {{\n\
110               \"{line}\": <line_number>,\n\
111               \"{severity}\": \"<{severities}>\",\n\
112               \"{category}\": \"<bug|security|performance|maintainability>\",\n\
113               \"{message}\": \"<clear description of the issue>\",\n\
114               \"{suggestion}\": \"<specific recommendation for fixing>\",\n\
115               \"{code_snippet}\": \"<the problematic code>\",\n\
116               \"{compile_failure}\": <true|false>\n\
117             }}\n\
118           ],\n\
119           \"{summary}\": \"<overall assessment of code quality>\"\n\
120         }}\n\
121         \n\
122         If no issues are found, return:\n\
123         {{\n\
124           \"{issues}\": [],\n\
125           \"{summary}\": \"No significant issues found. Code quality looks good.\"\n\
126         }}\n"
127    )
128}
129
130/// Build the language-specific concerns block, or an empty string when the
131/// language has no conventions.
132///
133/// Heading + one bullet per entry, trailing newline. The newline is consumed
134/// by the template's own following newline so a missing block leaves no
135/// blank hole and a present block does not leave a doubled one.
136fn conventions_block(language: &LanguageSupport) -> String {
137    if language.conventions.is_empty() {
138        return String::new();
139    }
140    let mut out = format!("**{}-specific concerns:**\n", language.display_name);
141    for concern in language.conventions {
142        out.push_str("- ");
143        out.push_str(concern);
144        out.push('\n');
145    }
146    out
147}