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

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

commit b65c069db3e7392ad65ef7ea706f2e28af1b13ee
Author: Jinal Sarvaiya <[email protected]>
AuthorDate: Sat Oct 24 08:18:09 2026 +0100

    fix #109: early-return bypass and development-snapshot doc qualifier
---
 .../camelkit/generator/OpenCodeConfigMerger.java   |  8 ++-
 .../camelkit/generator/OpenCodeGeneratorTest.java  | 77 ++++++++++++++++++++++
 docs/commands.md                                   |  2 +-
 docs/modules/ROOT/pages/agents.adoc                |  2 +-
 docs/modules/ROOT/pages/architecture.adoc          |  2 +-
 docs/modules/ROOT/pages/commands.adoc              |  5 +-
 docs/modules/ROOT/pages/mcp.adoc                   |  3 +-
 7 files changed, 92 insertions(+), 7 deletions(-)

diff --git 
a/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/OpenCodeConfigMerger.java
 
b/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/OpenCodeConfigMerger.java
index 847396d8..1ab51376 100644
--- 
a/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/OpenCodeConfigMerger.java
+++ 
b/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/OpenCodeConfigMerger.java
@@ -88,10 +88,16 @@ final class OpenCodeConfigMerger {
                 List.of("citrus_*").iterator());
     }
 
+    private boolean hasCitrusEntries(ObjectNode config) {
+        return config.path("mcp").has("citrus")
+                || config.path("permission").has("citrus_*");
+    }
+
     private String mergeGenerated(
             ExistingConfiguration existing, ObjectNode generated, boolean 
withCitrusMcp)
             throws IOException {
-        if (isCurrentAndOrdered(existing, generated)) {
+        boolean needsCitrusRemoval = !withCitrusMcp && 
hasCitrusEntries(existing.config());
+        if (isCurrentAndOrdered(existing, generated) && !needsCitrusRemoval) {
             return existing.content();
         }
 
diff --git 
a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/OpenCodeGeneratorTest.java
 
b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/OpenCodeGeneratorTest.java
index 28cdcaf4..cb4a920b 100644
--- 
a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/OpenCodeGeneratorTest.java
+++ 
b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/OpenCodeGeneratorTest.java
@@ -838,4 +838,81 @@ class OpenCodeGeneratorTest {
         assertTrue(withoutCitrus.contains("\"camel\""), "camel mcp must 
remain");
         assertTrue(withoutCitrus.contains("\"camel_*\""), "camel_* permission 
must remain");
     }
+
+    @Test
+    void regenerationRemovesCitrusWhenPermissionsReorderedBeforeGenerated() 
throws Exception {
+        new OpenCodeGenerator().generate(createContext("camel-kit", true));
+        Path configFile = tempDir.resolve("opencode.json");
+        String current = Files.readString(configFile);
+        assertTrue(current.contains("\"citrus_*\""), "citrus_* must be present 
after opt-in init");
+        int camelPos = current.indexOf("\"camel_*\"");
+        int citrusPos = current.indexOf("\"citrus_*\"");
+        assertTrue(camelPos < citrusPos, "camel_* must appear before citrus_* 
in generated output");
+
+        String reordered = current
+                .replaceFirst("\"camel_\\*\" : \"ask\",", "")
+                .replaceFirst("\"camel-knowledge_\\*\" : \"ask\",", "")
+                .replace("\"citrus_*\" : \"ask\"",
+                        "\"citrus_*\" : \"ask\",\n    \"camel_*\" : \"ask\",\n 
   \"camel-knowledge_*\" : \"ask\"");
+        assertTrue(reordered.indexOf("\"citrus_*\"") < 
reordered.indexOf("\"camel_*\""),
+                "citrus_* must now appear before camel_*");
+        Files.writeString(configFile, reordered);
+
+        new OpenCodeGenerator().generate(createContext("camel-kit", false));
+        String result = Files.readString(configFile);
+        assertFalse(result.contains("\"citrus\""), "citrus mcp must be 
removed");
+        assertFalse(result.contains("\"citrus_*\""), "citrus_* permission must 
be removed");
+        assertTrue(result.contains("\"camel\""), "camel mcp must remain");
+        assertTrue(result.contains("\"camel_*\""), "camel_* permission must 
remain");
+    }
+
+    @Test
+    void regenerationRemovesCitrusFromLowerLayerWithHigherLayer() throws 
Exception {
+        Files.writeString(tempDir.resolve("opencode.json"), """
+                {
+                  "permission": {"citrus_*": "ask", "camel_*": "ask"},
+                  "mcp": {
+                    "citrus": {"type": "local", "command": "jbang"},
+                    "camel": {"type": "local", "command": "jbang"}
+                  }
+                }
+                """);
+        Files.writeString(tempDir.resolve("opencode.jsonc"), """
+                {
+                  "permission": {"custom_rule": "deny"},
+                  "mcp": {"custom": {"type": "remote", "url": 
"https://example.test/mcp"}}
+                }
+                """);
+
+        new OpenCodeGenerator().generate(createContext("camel-kit", false));
+
+        JsonNode lower = readOpenCodeConfig(tempDir.resolve("opencode.json"));
+        assertFalse(lower.path("mcp").has("citrus"), "citrus must be removed 
from lower layer");
+        assertFalse(lower.path("permission").has("citrus_*"),
+                "citrus_* permission must be removed from lower layer");
+
+        JsonNode higher = 
readOpenCodeConfig(tempDir.resolve("opencode.jsonc"));
+        assertEquals("deny", 
higher.path("permission").path("custom_rule").asText(),
+                "unrelated user config must be preserved");
+        assertTrue(higher.path("mcp").has("custom"), "unrelated user mcp must 
be preserved");
+        assertTrue(higher.path("mcp").has("camel"), "camel mcp must remain in 
higher layer");
+    }
+
+    @Test
+    void regenerationRemovesCitrusOnlyFromPermissionWhenServerAlreadyGone() 
throws Exception {
+        new OpenCodeGenerator().generate(createContext("camel-kit", true));
+        Path configFile = tempDir.resolve("opencode.json");
+        String current = Files.readString(configFile);
+        assertTrue(current.contains("\"citrus_*\""), "citrus_* permission must 
be present");
+        String noServer = 
current.replaceAll("(?s)\"citrus\"\\s*:\\s*\\{[^}]*\\},?\\s*", "");
+        assertFalse(noServer.contains("\"citrus\""), "citrus server must be 
removed manually");
+        assertTrue(noServer.contains("\"citrus_*\""), "citrus_* permission 
must still be present");
+        Files.writeString(configFile, noServer);
+
+        new OpenCodeGenerator().generate(createContext("camel-kit", false));
+        String result = Files.readString(configFile);
+        assertFalse(result.contains("\"citrus_*\""),
+                "citrus_* permission must be removed after opt-out 
regeneration");
+        assertTrue(result.contains("\"camel\""), "camel mcp must remain");
+    }
 }
diff --git a/docs/commands.md b/docs/commands.md
index a0b61ded..ff565e44 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -66,7 +66,7 @@ camel kit init --here [options]
 | `--no-fetch` | `false` | Skip external catalog fetching |
 | `-p`, `--property` | -- | Override a config property (repeatable). Example: 
`-p "camel.main.version=4.18.4"` |
 | `-c`, `--config` | `~/.camel-kit/config.properties` | Path to a custom 
config properties file |
-| `--with-citrus-mcp` | `false` | Include the Citrus MCP server in agent 
configuration. When omitted, no Citrus server is generated; use this flag if 
your project needs Citrus test integration. |
+| `--with-citrus-mcp` | `false` | _(development snapshot only)_ Include the 
Citrus MCP server in agent configuration. When omitted, no Citrus server is 
generated; use this flag if your project needs Citrus test integration. 
Released versions (0.4.1 and earlier) include Citrus by default. |
 | `--source-platform` | `auto` | Source platform for migration: `mulesoft`, 
`camel`, `biztalk`, `auto` |
 | `--force` | `false` | Overwrite existing project without prompting (skips 
overwrite detection) |
 | `--silent` | `false` | Suppress all output (no banner, no TUI, no progress, 
no summary) -- useful for CI/scripted environments |
diff --git a/docs/modules/ROOT/pages/agents.adoc 
b/docs/modules/ROOT/pages/agents.adoc
index 423259ab..4302dc9f 100644
--- a/docs/modules/ROOT/pages/agents.adoc
+++ b/docs/modules/ROOT/pages/agents.adoc
@@ -19,7 +19,7 @@ workflow is the same everywhere and only the way you invoke 
it differs.
 |===
 
 The generated assets and the MCP configuration file of each target are listed 
in
-xref:index.adoc#agents[the overview]. Every target gets two MCP servers by 
default — `camel` (catalog, route
+xref:index.adoc#agents[the overview]. Every target gets two MCP servers by 
default _(development snapshot; released versions include all three)_ — `camel` 
(catalog, route
 validation, migration and security analysis) and `camel-knowledge` 
(documentation, CVE, release and component search)
 — with an optional third, `citrus` (test actions, endpoints and schemas), 
enabled via `--with-citrus-mcp`. All are configured as local JBang processes 
whose versions come from the
 Camel Kit distribution. See xref:mcp.adoc[].
diff --git a/docs/modules/ROOT/pages/architecture.adoc 
b/docs/modules/ROOT/pages/architecture.adoc
index d1228703..1baf5e08 100644
--- a/docs/modules/ROOT/pages/architecture.adoc
+++ b/docs/modules/ROOT/pages/architecture.adoc
@@ -31,7 +31,7 @@ front matter against it.
 | `camel-kit-core/src/main/resources/skills/`, copied into the agent's skills 
directory by `init`
 
 | 3. MCP servers
-| Two local servers by default (Camel catalog for verification and validation, 
Knowledge for documentation search),
+| Two local servers by default _(development snapshot; released versions 
include all three)_ (Camel catalog for verification and validation, Knowledge 
for documentation search),
   with an optional third (Citrus for test guidance, enabled via 
`--with-citrus-mcp`)
 | The agent's MCP configuration file, generated by `init`
 
diff --git a/docs/modules/ROOT/pages/commands.adoc 
b/docs/modules/ROOT/pages/commands.adoc
index 969ee2c4..18f4fce2 100644
--- a/docs/modules/ROOT/pages/commands.adoc
+++ b/docs/modules/ROOT/pages/commands.adoc
@@ -31,8 +31,9 @@ camel-kit init --here [options]
 | `--source-platform` | `auto` | Source platform of a migration: `mulesoft`, 
`biztalk`, `camel` or `auto`
 | `--citrus-version` | the configured `citrus.version` ({citrus-version}) | 
Citrus version for the test schemas
   and the generated test dependencies
-| `--with-citrus-mcp` | `false` | Include the Citrus MCP server in agent 
configuration. When omitted, no Citrus
-  server is generated; use this flag if your project needs Citrus test 
integration.
+| `--with-citrus-mcp` | `false` | _(development snapshot only)_ Include the 
Citrus MCP server in agent
+  configuration. When omitted, no Citrus server is generated; use this flag if 
your project needs
+  Citrus test integration. Released versions (0.4.1 and earlier) include 
Citrus by default.
 | `--no-fetch` | `false` | Skip the external catalog fetch
 | `-p`, `--property` | | Override a configuration property, repeatable: `-p 
"camel.main.version=4.18.4"`
 | `-c`, `--config` | `~/.camel-kit/config.properties` | Configuration 
properties file
diff --git a/docs/modules/ROOT/pages/mcp.adoc b/docs/modules/ROOT/pages/mcp.adoc
index 32febbcb..2e6ce784 100644
--- a/docs/modules/ROOT/pages/mcp.adoc
+++ b/docs/modules/ROOT/pages/mcp.adoc
@@ -1,7 +1,8 @@
 = MCP servers
 
 Camel Kit uses the Model Context Protocol to verify Camel configuration, to 
guide Citrus test generation and to
-search documentation without loading whole catalogs into the agent's context. 
`init` configures two local servers by default (three with `--with-citrus-mcp`)
+search documentation without loading whole catalogs into the agent's context. 
`init` configures two local servers by default (three with `--with-citrus-mcp`;
+released versions 0.4.1 and earlier include all three by default)
 in the agent's MCP configuration file (see xref:agents.adoc[] for the file of 
each target); the agent starts each
 one with JBang when a skill first needs it, and JBang resolves the runner and 
the catalog artifacts from the
 configured repositories.

Reply via email to