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.
