Skip to main content

intent_implement/
prompt.rs

1//! Prompt construction for AI-powered implementation generation.
2//!
3//! Builds system prompts and user messages that give the LLM enough context
4//! to produce compilable, contract-aware implementations from intent specs.
5
6use crate::context::PromptContext;
7use intent_codegen::Language;
8
9/// Build the system prompt for implementation generation.
10pub fn system_prompt(lang: Language) -> String {
11    let lang_name = language_name(lang);
12    let lang_guidance = language_guidance(lang);
13
14    format!(
15        "{ROLE}\n\n\
16         ## Target Language: {lang_name}\n\n\
17         {lang_guidance}\n\n\
18         {IMPLEMENTATION_RULES}"
19    )
20}
21
22/// Build the user message with spec, skeleton, contracts, and test harness.
23pub fn user_message(context: &PromptContext, lang: Language) -> String {
24    let lang_name = language_name(lang);
25    let lang_tag = language_tag(lang);
26
27    let mut msg = format!(
28        "Generate a complete {lang_name} implementation for the following IntentLang specification.\n\n\
29         ## Specification\n\n\
30         ```intent\n{spec}\n```\n\n\
31         ## Skeleton Code\n\n\
32         Start from this skeleton and implement all function bodies:\n\n\
33         ```{lang_tag}\n{skeleton}\n```\n\n\
34         ## Contracts\n\n\
35         {contracts}",
36        spec = context.spec_source.trim(),
37        skeleton = context.skeleton.trim(),
38        contracts = context.contracts.trim(),
39    );
40
41    if !context.test_harness.is_empty() {
42        msg.push_str(&format!(
43            "\n\n## Contract Tests\n\n\
44             Include this test module at the bottom of your file. \
45             Your implementation MUST make all tests pass. \
46             Entity-typed parameters must accept `&mut` references so tests can \
47             verify postconditions on the mutated state.\n\n\
48             ```{lang_tag}\n{harness}\n```",
49            harness = context.test_harness.trim(),
50        ));
51    }
52
53    msg.push_str(
54        "\n\nRespond with ONLY the complete source file. No markdown fences, no explanation.",
55    );
56    msg
57}
58
59/// Build a retry message with validation errors.
60pub fn retry_message(code: &str, errors: &[String], lang: Language) -> String {
61    let lang_name = language_name(lang);
62    let error_list = errors
63        .iter()
64        .enumerate()
65        .map(|(i, e)| format!("{}. {}", i + 1, e))
66        .collect::<Vec<_>>()
67        .join("\n");
68
69    format!(
70        "The generated {lang_name} code has validation errors:\n\n\
71         {error_list}\n\n\
72         Here was the code:\n```\n{code}\n```\n\n\
73         Fix the errors and respond with ONLY the corrected source file. \
74         No explanation, no markdown fences."
75    )
76}
77
78fn language_name(lang: Language) -> &'static str {
79    match lang {
80        Language::Rust => "Rust",
81        Language::TypeScript => "TypeScript",
82        Language::Python => "Python",
83        Language::Go => "Go",
84        Language::Java => "Java",
85        Language::CSharp => "C#",
86        Language::Swift => "Swift",
87    }
88}
89
90fn language_tag(lang: Language) -> &'static str {
91    match lang {
92        Language::Rust => "rust",
93        Language::TypeScript => "typescript",
94        Language::Python => "python",
95        Language::Go => "go",
96        Language::Java => "java",
97        Language::CSharp => "csharp",
98        Language::Swift => "swift",
99    }
100}
101
102fn language_guidance(lang: Language) -> &'static str {
103    match lang {
104        Language::Rust => RUST_GUIDANCE,
105        Language::TypeScript => TYPESCRIPT_GUIDANCE,
106        Language::Python => PYTHON_GUIDANCE,
107        Language::Go => GO_GUIDANCE,
108        Language::Java => JAVA_GUIDANCE,
109        Language::CSharp => CSHARP_GUIDANCE,
110        Language::Swift => SWIFT_GUIDANCE,
111    }
112}
113
114const ROLE: &str = "\
115You are a code implementation generator. Given an IntentLang specification, \
116its skeleton code (typed stubs), and a contracts summary, you produce a \
117complete, working implementation. You output ONLY raw source code — no \
118markdown fences, no explanations, no commentary.";
119
120const IMPLEMENTATION_RULES: &str = "\
121## Implementation Rules
122
1231. Start from the provided skeleton code (structs, function signatures, types).
1242. Implement every function body — replace all `todo!()`, `throw`, or `raise` stubs.
1253. Honor all preconditions (requires) as runtime checks. Return an error or panic \
126   if a precondition is violated.
1274. Honor all postconditions (ensures). Where `old(x)` appears, capture the \
128   pre-state value before mutation and verify the postcondition holds.
1295. Honor all invariant constraints in the implementation logic.
1306. Handle all edge cases listed in the contracts summary.
1317. Use idiomatic patterns for the target language.
1328. Do not add new public types or functions beyond what the skeleton defines. \
133   You may add private helpers.
1349. Do not import external crates/packages beyond the standard library unless \
135   the skeleton already imports them.
13610. Output ONLY the complete source file. No markdown fences, no explanation.";
137
138const RUST_GUIDANCE: &str = "\
139- Use `Result<T, E>` for fallible operations. Define a local error enum if needed.
140- For `old(expr)`: clone the value before mutation, then assert the postcondition.
141- Use `#[derive(Debug, Clone, PartialEq)]` on structs.
142- Decimal types map to `f64` (or a decimal library if one is in scope).
143- UUID maps to `String` unless a uuid crate is imported.
144- Use `assert!()` or return `Err` for precondition/postcondition violations.";
145
146const TYPESCRIPT_GUIDANCE: &str = "\
147- Use `throw new Error(...)` for precondition violations.
148- For `old(expr)`: use spread/destructuring to capture pre-state before mutation.
149- Decimal types map to `number`.
150- UUID maps to `string`.
151- Use TypeScript strict mode idioms (explicit types, no `any`).";
152
153const PYTHON_GUIDANCE: &str = "\
154- Use `raise ValueError(...)` for precondition violations.
155- For `old(expr)`: use `copy.deepcopy()` or explicit capture before mutation.
156- Decimal types map to `Decimal` from the `decimal` module.
157- UUID maps to `str` (or `uuid.UUID` if imported).
158- Use type hints throughout.";
159
160const GO_GUIDANCE: &str = "\
161- Return `error` values for precondition violations. Use `fmt.Errorf(...)`.
162- For `old(expr)`: copy the struct value before mutation, then verify postconditions.
163- Decimal types map to `float64`.
164- UUID maps to `string`.
165- Use exported (PascalCase) names for public structs and functions.
166- Use JSON struct tags matching the field names.";
167
168const JAVA_GUIDANCE: &str = "\
169- Use `throw new IllegalArgumentException(...)` for precondition violations.
170- For `old(expr)`: copy the record value before mutation, then verify postconditions.
171- Decimal types map to `BigDecimal`.
172- UUID maps to `UUID` from `java.util.UUID`.
173- Use Java 16+ records for data classes.
174- Use camelCase for method names, PascalCase for class/record names.";
175
176const CSHARP_GUIDANCE: &str = "\
177- Use `throw new ArgumentException(...)` for precondition violations.
178- For `old(expr)`: copy the record value with `with {}` before mutation.
179- Decimal types map to `decimal`.
180- UUID maps to `Guid`.
181- Use C# 10+ records and file-scoped namespaces.
182- Use PascalCase for all public members.";
183
184const SWIFT_GUIDANCE: &str = "\
185- Use `throw` or `preconditionFailure(...)` for precondition violations.
186- For `old(expr)`: copy the struct value before mutation, then verify postconditions.
187- Decimal types map to `Decimal` from Foundation.
188- UUID maps to `UUID` from Foundation.
189- Use structs with Codable conformance for entities.
190- Use camelCase for functions/properties, PascalCase for types.";
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn test_system_prompt_contains_role_and_rules() {
198        let prompt = system_prompt(Language::Rust);
199        assert!(prompt.contains("code implementation generator"));
200        assert!(prompt.contains("Implementation Rules"));
201        assert!(prompt.contains("Rust"));
202    }
203
204    #[test]
205    fn test_system_prompt_language_specific() {
206        let ts = system_prompt(Language::TypeScript);
207        assert!(ts.contains("TypeScript"));
208        assert!(ts.contains("throw new Error"));
209
210        let py = system_prompt(Language::Python);
211        assert!(py.contains("Python"));
212        assert!(py.contains("raise ValueError"));
213    }
214
215    #[test]
216    fn test_user_message_includes_all_sections() {
217        let ctx = crate::context::PromptContext {
218            spec_source: "module Test\n".to_string(),
219            skeleton: "struct Test {}\n".to_string(),
220            contracts: "### Action: Foo\n".to_string(),
221            test_harness: String::new(),
222        };
223        let msg = user_message(&ctx, Language::Rust);
224
225        assert!(msg.contains("module Test"));
226        assert!(msg.contains("struct Test"));
227        assert!(msg.contains("Action: Foo"));
228        assert!(msg.contains("No markdown fences"));
229    }
230
231    #[test]
232    fn test_user_message_includes_test_harness() {
233        let ctx = crate::context::PromptContext {
234            spec_source: "module Test\n".to_string(),
235            skeleton: "struct Test {}\n".to_string(),
236            contracts: "### Action: Foo\n".to_string(),
237            test_harness: "#[cfg(test)]\nmod contract_tests {\n    fn test_foo() {}\n}\n"
238                .to_string(),
239        };
240        let msg = user_message(&ctx, Language::Rust);
241
242        assert!(msg.contains("Contract Tests"));
243        assert!(msg.contains("mod contract_tests"));
244        assert!(msg.contains("&mut"));
245        assert!(msg.contains("MUST make all tests pass"));
246    }
247
248    #[test]
249    fn test_user_message_skips_empty_harness() {
250        let ctx = crate::context::PromptContext {
251            spec_source: "module Test\n".to_string(),
252            skeleton: "struct Test {}\n".to_string(),
253            contracts: "### Action: Foo\n".to_string(),
254            test_harness: String::new(),
255        };
256        let msg = user_message(&ctx, Language::Rust);
257
258        assert!(!msg.contains("Contract Tests"));
259    }
260
261    #[test]
262    fn test_retry_message_lists_errors() {
263        let msg = retry_message(
264            "fn main() {}",
265            &["missing struct Foo".into(), "unbalanced braces".into()],
266            Language::Rust,
267        );
268        assert!(msg.contains("1. missing struct Foo"));
269        assert!(msg.contains("2. unbalanced braces"));
270        assert!(msg.contains("fn main()"));
271    }
272}