ep: Update teacher prompt to reduce edit reversals (#48022)

Oleksiy Syvokon created

Release Notes:

- N/A

Change summary

crates/edit_prediction_cli/src/example.rs         | 21 +++-
crates/edit_prediction_cli/src/format_prompt.rs   | 20 ++++
crates/edit_prediction_cli/src/prompts/teacher.md | 83 ++++++++++++++++
3 files changed, 116 insertions(+), 8 deletions(-)

Detailed changes

crates/edit_prediction_cli/src/example.rs 🔗

@@ -243,14 +243,23 @@ pub fn sort_examples_by_repo_and_rev(examples: &mut [Example]) {
 }
 
 pub fn group_examples_by_repo(examples: Vec<Example>) -> VecDeque<Vec<Example>> {
-    let mut examples_by_repo = HashMap::default();
+    let mut examples_by_repo: HashMap<String, Vec<Example>> = HashMap::default();
+    let mut ungrouped = Vec::new();
     for example in examples {
-        examples_by_repo
-            .entry(example.spec.repository_url.clone())
-            .or_insert_with(Vec::new)
-            .push(example);
+        if example.spec.repository_url.is_empty() {
+            ungrouped.push(example);
+        } else {
+            examples_by_repo
+                .entry(example.spec.repository_url.clone())
+                .or_insert_with(Vec::new)
+                .push(example);
+        }
+    }
+    let mut result: VecDeque<Vec<Example>> = examples_by_repo.into_values().collect();
+    for example in ungrouped {
+        result.push_back(vec![example]);
     }
-    examples_by_repo.into_values().collect()
+    result
 }
 
 fn parse_markdown_example(input: &str) -> Result<Example> {

crates/edit_prediction_cli/src/format_prompt.rs 🔗

@@ -168,6 +168,7 @@ impl TeacherPrompt {
     pub(crate) const EDITABLE_REGION_START: &str = "<|editable_region_start|>\n";
     pub(crate) const EDITABLE_REGION_END: &str = "\n<|editable_region_end|>";
     pub(crate) const USER_CURSOR_MARKER: &str = "<|user_cursor|>";
+    pub(crate) const NO_EDITS: &str = "NO_EDITS";
 
     /// Truncate edit history to this number of last lines
     const MAX_HISTORY_LINES: usize = 128;
@@ -193,6 +194,12 @@ impl TeacherPrompt {
         // Extract updated (new) editable region from the model response.
         // The model may include editable region markers in its output, so we need to strip them.
         let new_editable_region = extract_last_codeblock(response);
+
+        // Check if the model indicated no edits are needed
+        if new_editable_region.trim() == Self::NO_EDITS {
+            return Ok(String::new());
+        }
+
         let mut new_editable_region = Self::extract_editable_region(&new_editable_region)?;
         let old_editable_region = Self::extract_editable_region(
             &example
@@ -551,6 +558,19 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_parse_no_edits_response() {
+        let response = indoc::indoc! {"
+            The code is already complete. There is no clear next edit to make.
+
+            `````
+            NO_EDITS
+            `````
+        "};
+        let codeblock = extract_last_codeblock(response);
+        assert_eq!(codeblock.trim(), TeacherPrompt::NO_EDITS);
+    }
+
     #[test]
     fn test_extract_editable_region_strips_cursor_marker() {
         let text = indoc::indoc! {"

crates/edit_prediction_cli/src/prompts/teacher.md 🔗

@@ -14,12 +14,18 @@ You are an edit prediction assistant in a code editor. Your task is to predict t
 
 ## Rules
 
+- **NEVER undo or revert the user's recent edits.** Examine the diff in the edit history carefully:
+  - If a line was removed (starts with `-`), do NOT restore that content—even if the code now appears incomplete or broken without it
+  - If a line was added (starts with `+`), do NOT delete or significantly modify it
+  - If code appears broken or incomplete after the user's edit, output `NO_EDITS` rather than "fixing" it by reverting
+  - Only add NEW content that extends the user's work forward; never restore what they removed
+  - **Key test**: if your prediction would make the code more similar to what it was BEFORE the user's edit, output `NO_EDITS` instead
+  - **Never assume a deletion was accidental.** Even if removing content breaks the code, breaks a pattern, or leaves text looking "incomplete", respect it. The user may be mid-rewrite. Do NOT "complete" partial text by restoring what was deleted.
 - Do not just mechanically apply patterns - reason about what changes make sense given the context and the programmer's apparent goals.
 - Do not just fix syntax errors - look for the broader refactoring pattern and apply it systematically throughout the code.
 - Keep existing formatting unless it's absolutely necessary
 - When edit history and surrounding code suggest different edits, prioritize the most recent edits in the history as they best reflect current intent.
 - When uncertain, predict only the minimal, high-confidence portion of the edit. Prefer a small, correct prediction over a large, speculative one
-- Do not delete or remove text that was just added in the edit history. If a recent edit introduces incomplete or incorrect code, finish or fix it in place, or simply do nothing rather than removing it. Only remove a recent edit if the history explicitly shows the user undoing it themselves.
 - Treat partial text at or near the cursor as the beginning of something the user is actively typing. Complete the code the user appears to be creating based on context.
 
 # Input Format
@@ -36,8 +42,12 @@ You will be provided with:
 
 - Briefly explain the user's current intent based on the edit history and their current cursor location.
 - Output a markdown codeblock containing **only** the editable region with your predicted edits applied. The codeblock must start with `<|editable_region_start|>` and end with `<|editable_region_end|>`. Do not include any content before or after these tags.
+- If no edit is needed (the code is already complete and correct, or there is no clear next edit to make), output a codeblock containing only `NO_EDITS`:
+  `````
+  NO_EDITS
+  `````
 - If the next edit has some uncertainty, you may still predict the surrounding code (such as a function definition, `for` loop, etc) and place the `<|user_cursor|>` within it for the user to fill in.
-  -e.g. if a user is typing `func<|user_cursor|>`, but you don't know what the function name should be, you can predict `function <|user_cursor|>() {}`
+  - e.g. if a user is typing `func<|user_cursor|>`, but you don't know what the function name should be, you can predict `function <|user_cursor|>() {}`
 
 ## Example 1
 
@@ -133,9 +143,78 @@ The user is clearly starting to type `eprintln!()`, however, what they intend to
 fn handle_close_button_click(modal_state: &mut ModalState, evt: &Event) {
     modal_state.close();
     eprintln!("<|user_cursor|>");
+    modal_state.dismiss();
 <|editable_region_end|>
 `````
 
+## Example 3
+
+The code is already complete and there is no clear next edit to make. You should output NO_EDITS.
+
+### User Edit History
+
+`````
+--- a/src/utils.rs
++++ b/src/utils.rs
+@@ -10,7 +10,7 @@
+ fn add(a: i32, b: i32) -> i32 {
+-    a - b
++    a + b
+ }
+`````
+
+### Current File
+
+`````src/utils.rs
+<|editable_region_start|>
+fn add(a: i32, b: i32) -> i32 {
+    a + b<|user_cursor|>
+}
+<|editable_region_end|>
+`````
+
+### Output
+
+The user just fixed a bug in the `add` function, changing subtraction to addition. The code is now correct and complete. There is no clear next edit to make.
+
+`````
+NO_EDITS
+`````
+
+## Example 4
+
+The user just deleted code, leaving behind what looks incomplete. You must NOT "complete" it by restoring deleted content—that would undo their edit. Output NO_EDITS. **This is the correct response even though the code appears broken.**
+
+### User Edit History
+
+`````
+--- a/config.nix
++++ b/config.nix
+@@ -10,7 +10,7 @@
+     # /etc/modular/crashdb needs to be mutable
+-    ln -s /tmp/crashdb $out/etc/modular/crashdb
++    ln -s /tmp/cr $out/etc/modular/crashdb
+   '';
+`````
+
+### Current File
+
+`````config.nix
+<|editable_region_start|>
+    # /etc/modular/crashdb needs to be mutable
+    ln -s /tmp/cr<|user_cursor|> $out/etc/modular/crashdb
+  '';
+<|editable_region_end|>
+`````
+
+### Output
+
+The user deleted `ashdb` from `/tmp/crashdb`, leaving `/tmp/cr`. Although this looks like incomplete text that I could "complete", doing so would restore deleted content. The user intentionally removed that text—I must not undo their deletion.
+
+`````
+NO_EDITS
+`````
+
 
 # Your task: