intent_implement/
prompt.rs1use crate::context::PromptContext;
7use intent_codegen::Language;
8
9pub 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
22pub 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
59pub 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}