This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new ad870c089b68 chore: camel-jbang - camel_write_file and camel_edit_file 
refuse the problems a write brings, not the ones the file had (#27190)
ad870c089b68 is described below

commit ad870c089b68d55b410a983dd9d583be844da35a
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 1 12:55:58 2026 +0200

    chore: camel-jbang - camel_write_file and camel_edit_file refuse the 
problems a write brings, not the ones the file had (#27190)
    
    camel_write_file and camel_edit_file refuse only the problems a write 
brings, not the ones the file already had.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../dsl/jbang/core/commands/ai/AuthoringTools.java | 44 ++++++++++++
 .../ai/AuthoringToolsExistingProblemsTest.java     | 82 ++++++++++++++++++++++
 .../dsl/jbang/core/commands/tui/McpFacade.java     | 10 +++
 3 files changed, 136 insertions(+)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
index 064ffd66967e..298c1d615a4b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
@@ -29,6 +29,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -829,6 +830,35 @@ public final class AuthoringTools {
         return n;
     }
 
+    /**
+     * The problems of the new content that the file did not have: a problem 
is the same when its message is, wherever
+     * its line moved to (as many times as it was there).
+     *
+     * @param stillThere gets the problems of the new content the file already 
had
+     */
+    public static List<String> newProblems(List<String> before, List<String> 
after, List<String> stillThere) {
+        Map<String, Integer> known = new HashMap<>();
+        for (String b : before) {
+            known.merge(withoutLine(b), 1, Integer::sum);
+        }
+        List<String> answer = new ArrayList<>();
+        for (String a : after) {
+            String key = withoutLine(a);
+            Integer n = known.get(key);
+            if (n != null && n > 0) {
+                known.put(key, n - 1);
+                stillThere.add(a);
+            } else {
+                answer.add(a);
+            }
+        }
+        return answer;
+    }
+
+    private static String withoutLine(String message) {
+        return message != null ? message.replaceFirst("^Line \\d+: ", "") : "";
+    }
+
     /** Writes a file after validating it, as {@code camel_write_file} does; 
no confirmation is asked here. */
     public static JsonObject writeFile(ToolContext ctx, Path dir, String file, 
String content, boolean validate) {
         Path path = resolveFile(dir, file);
@@ -836,10 +866,20 @@ public final class AuthoringTools {
         if (exists && !Files.isRegularFile(path)) {
             throw new ToolExecutionException(file + " is not a regular file");
         }
+        List<String> problemsBefore = List.of();
         if (validate && SourceValidator.isValidatableFile(file)) {
             // a missing consumer of a direct: endpoint does not refuse the 
write: it is often a file not written yet
             List<String> errors = SourceValidator.validate(file, content, 
ctx.catalog(), ctx.propertyLineValidator(), dir,
                     null, false);
+            if (!errors.isEmpty() && exists) {
+                // the problems the file already had do not refuse the write: 
an edit that fixes one problem would be
+                // refused for the others, and the agent made to fix them all; 
only the problems the write brings are
+                problemsBefore = SourceValidator.validate(file, read(path), 
ctx.catalog(), ctx.propertyLineValidator(),
+                        dir, null, false);
+                List<String> stillThere = new ArrayList<>();
+                errors = newProblems(problemsBefore, errors, stillThere);
+                problemsBefore = stillThere;
+            }
             if (!errors.isEmpty()) {
                 JsonObject result = new JsonObject();
                 result.put("status", "invalid");
@@ -872,6 +912,10 @@ public final class AuthoringTools {
         result.put("directory", dir.toString());
         result.put("lines", content.isEmpty() ? 0 : (int) 
content.lines().count());
         result.put("bytes", content.getBytes(StandardCharsets.UTF_8).length);
+        if (!problemsBefore.isEmpty()) {
+            // written with problems the file already had: said, so they are 
not taken for fixed
+            result.put("existingProblems", new JsonArray(problemsBefore));
+        }
         if (watch) {
             JsonObject reload = ReloadOutcome.await(ctx.pid(), processName, 
sinceKey, RELOAD_WAIT_MILLIS);
             result.put("reload", reload);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsExistingProblemsTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsExistingProblemsTest.java
new file mode 100644
index 000000000000..10a094274dc8
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsExistingProblemsTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.ai;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * An edit or write is refused for the problems it brings, not for the ones 
the file already had: an edit that fixes one
+ * problem of a file with two is written.
+ */
+class AuthoringToolsExistingProblemsTest {
+
+    private static final String ROUTE = """
+            import org.apache.camel.builder.RouteBuilder;
+
+            public class MyRoute extends RouteBuilder {
+                @Override
+                public void configure() throws Exception {
+                    from("timer:tick?peroid=1000")
+                        .to("seda:out?siz=10");
+                }
+            }
+            """;
+
+    @TempDir
+    Path dir;
+
+    @Test
+    void anEditThatFixesOneOfTwoProblemsIsWritten() throws Exception {
+        Files.writeString(dir.resolve("MyRoute.java"), ROUTE);
+        JsonObject result = AuthoringTools.editFile(new ToolContext(), dir, 
"MyRoute.java", "siz=10", "size=10");
+        assertThat(result.getString("status")).isEqualTo("edited");
+        
assertThat(Files.readString(dir.resolve("MyRoute.java"))).contains("size=10");
+        // the problem the file still has is said, so it is not taken for fixed
+        JsonArray existing = (JsonArray) result.get("existingProblems");
+        assertThat(existing).hasSize(1);
+        assertThat(existing.getString(0)).contains("peroid");
+    }
+
+    @Test
+    void anEditThatBringsAProblemIsRefused() throws Exception {
+        Files.writeString(dir.resolve("MyRoute.java"), ROUTE);
+        JsonObject result = AuthoringTools.editFile(new ToolContext(), dir, 
"MyRoute.java", "seda:out", "seda:out2?fooBar=1&x");
+        assertThat(result.getString("status")).isEqualTo("invalid");
+        assertThat(result.toJson()).contains("fooBar");
+        
assertThat(Files.readString(dir.resolve("MyRoute.java"))).isEqualTo(ROUTE);
+    }
+
+    @Test
+    void aProblemIsTheSameWhereverItsLineMoved() {
+        List<String> stillThere = new ArrayList<>();
+        List<String> added = AuthoringTools.newProblems(
+                List.of("Line 6: timer: Unknown option 'peroid'", "Line 7: x"),
+                List.of("Line 8: timer: Unknown option 'peroid'", "Line 9: 
y"), stillThere);
+        assertThat(added).containsExactly("Line 9: y");
+        assertThat(stillThere).containsExactly("Line 8: timer: Unknown option 
'peroid'");
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
index bc2a582a4c41..abb818bc1628 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
@@ -1142,6 +1142,16 @@ class McpFacade {
         }
         if (sourceValidator != null && 
SourceValidator.isValidatableFile(file)) {
             List<String> errors = sourceValidator.apply(file, content);
+            if (!errors.isEmpty() && exists) {
+                // only the problems the write brings refuse it, not the ones 
the file already had: an edit that fixes
+                // one problem would be refused for the others
+                try {
+                    List<String> before = sourceValidator.apply(file, 
Files.readString(filePath, StandardCharsets.UTF_8));
+                    errors = AuthoringTools.newProblems(before, errors, new 
ArrayList<>());
+                } catch (IOException e) {
+                    // the file as it was is not known: every problem refuses 
the write
+                }
+            }
             if (!errors.isEmpty()) {
                 JsonObject result = new JsonObject();
                 result.put("status", "invalid");

Reply via email to