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");