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 8fe5fbe74ceae9131abdd95fa9bfbafaef251213 Author: Jinal Sarvaiya <[email protected]> AuthorDate: Tue Oct 20 14:24:25 2026 +0100 fix #243: make camel-kit skills discoverable and correctly scoped in Claude Code --- .../camelkit/generator/SkillResourceInstaller.java | 10 +++- .../src/main/resources/agents/registry/claude.yaml | 2 +- .../resources/skills/camel-brainstorm/SKILL.md | 2 +- .../src/main/resources/skills/camel-debug/SKILL.md | 2 +- .../main/resources/skills/camel-execute/SKILL.md | 2 +- .../main/resources/skills/camel-knowledge/SKILL.md | 2 +- .../main/resources/skills/camel-migrate/SKILL.md | 2 +- .../src/main/resources/skills/camel-plan/SKILL.md | 2 +- .../src/main/resources/skills/camel-ship/SKILL.md | 2 +- .../src/main/resources/skills/camel-start/SKILL.md | 2 +- .../main/resources/skills/camel-validate/SKILL.md | 2 +- .../main/resources/templates/claude/camel-ship.md | 2 +- .../camelkit/config/AgentRegistryTest.java | 2 +- .../camelkit/generator/ClaudeGeneratorTest.java | 60 ++++++++++++++++++++-- .../generator/ShippedAssetStructureTest.java | 3 +- 15 files changed, 78 insertions(+), 19 deletions(-) diff --git a/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/SkillResourceInstaller.java b/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/SkillResourceInstaller.java index 44fe579c..3832a8e1 100644 --- a/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/SkillResourceInstaller.java +++ b/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/SkillResourceInstaller.java @@ -156,7 +156,8 @@ class SkillResourceInstaller { } } if (AgentGeneratorStrategy.BOB2.descriptorValue().equals(ctx.agentName()) - || AgentGeneratorStrategy.QWEN.descriptorValue().equals(ctx.agentName())) { + || AgentGeneratorStrategy.QWEN.descriptorValue().equals(ctx.agentName()) + || AgentGeneratorStrategy.CLAUDE.descriptorValue().equals(ctx.agentName())) { // Bob Shell's native skill picker hides false values; same-name command stubs cannot override them. boolean exposeCommand = AgentGeneratorStrategy.BOB2.descriptorValue().equals(ctx.agentName()) && workflow.commands().stream().anyMatch(command -> command.userFacing() @@ -166,6 +167,9 @@ class SkillResourceInstaller { if (AgentGeneratorStrategy.COPILOT.descriptorValue().equals(ctx.agentName())) { addCopilotReadableInternalSkillMetadata(destination); } + if (AgentGeneratorStrategy.CLAUDE.descriptorValue().equals(ctx.agentName())) { + addClaudeReadableInternalSkillMetadata(destination); + } if (AgentGeneratorStrategy.PI.descriptorValue().equals(ctx.agentName())) { addPiReadableInternalSkillMetadata(destination); } @@ -238,6 +242,10 @@ class SkillResourceInstaller { } } + void addClaudeReadableInternalSkillMetadata(Path skillFile) throws Exception { + addInternalSkillInvocationMetadata(skillFile, "Claude"); + } + void addCopilotReadableInternalSkillMetadata(Path skillFile) throws Exception { addInternalSkillInvocationMetadata(skillFile, "Copilot"); } diff --git a/camel-kit-core/src/main/resources/agents/registry/claude.yaml b/camel-kit-core/src/main/resources/agents/registry/claude.yaml index 1795c7bc..bbb67314 100644 --- a/camel-kit-core/src/main/resources/agents/registry/claude.yaml +++ b/camel-kit-core/src/main/resources/agents/registry/claude.yaml @@ -2,7 +2,7 @@ id: claude displayName: Claude Code commandDirectory: .claude/commands skillsDirectory: .claude/skills -generatesCommandStubs: true +generatesCommandStubs: false commandFileFormat: md argumentPlaceholder: $ARGUMENTS mcpConfigPath: .mcp.json diff --git a/camel-kit-core/src/main/resources/skills/camel-brainstorm/SKILL.md b/camel-kit-core/src/main/resources/skills/camel-brainstorm/SKILL.md index e95c14c6..c1699482 100644 --- a/camel-kit-core/src/main/resources/skills/camel-brainstorm/SKILL.md +++ b/camel-kit-core/src/main/resources/skills/camel-brainstorm/SKILL.md @@ -1,6 +1,6 @@ --- name: camel-brainstorm -description: Design and plan Camel integrations through collaborative dialogue. +description: Use when designing a new Camel integration through collaborative dialogue to produce a design spec. user_invocable: false --- diff --git a/camel-kit-core/src/main/resources/skills/camel-debug/SKILL.md b/camel-kit-core/src/main/resources/skills/camel-debug/SKILL.md index 9b5a8e45..fe82e3ce 100644 --- a/camel-kit-core/src/main/resources/skills/camel-debug/SKILL.md +++ b/camel-kit-core/src/main/resources/skills/camel-debug/SKILL.md @@ -1,6 +1,6 @@ --- name: camel-debug -description: Ad-hoc troubleshooting for broken Camel routes outside of a pipeline run. +description: Use when a Camel route is broken and needs ad-hoc troubleshooting outside of a pipeline run. user_invocable: false --- diff --git a/camel-kit-core/src/main/resources/skills/camel-execute/SKILL.md b/camel-kit-core/src/main/resources/skills/camel-execute/SKILL.md index 97d1c696..78164782 100644 --- a/camel-kit-core/src/main/resources/skills/camel-execute/SKILL.md +++ b/camel-kit-core/src/main/resources/skills/camel-execute/SKILL.md @@ -1,6 +1,6 @@ --- name: camel-execute -description: Execute a ready implementation plan derived from an approved design with an adversarial pre-filter and ordered spec and quality review. +description: Use when executing a ready implementation plan with adversarial pre-filter and ordered spec and quality review. user_invocable: false --- diff --git a/camel-kit-core/src/main/resources/skills/camel-knowledge/SKILL.md b/camel-kit-core/src/main/resources/skills/camel-knowledge/SKILL.md index 373e8e43..f3265974 100644 --- a/camel-kit-core/src/main/resources/skills/camel-knowledge/SKILL.md +++ b/camel-kit-core/src/main/resources/skills/camel-knowledge/SKILL.md @@ -1,6 +1,6 @@ --- name: camel-knowledge -description: Look up Camel documentation, components, CVEs/security advisories, and versions. +description: Use when looking up Apache Camel documentation, components, CVEs/security advisories, or version information. user_invocable: false --- diff --git a/camel-kit-core/src/main/resources/skills/camel-migrate/SKILL.md b/camel-kit-core/src/main/resources/skills/camel-migrate/SKILL.md index 96b2f6e7..f1d00b73 100644 --- a/camel-kit-core/src/main/resources/skills/camel-migrate/SKILL.md +++ b/camel-kit-core/src/main/resources/skills/camel-migrate/SKILL.md @@ -1,6 +1,6 @@ --- name: camel-migrate -description: Migrate an existing integration from another product to Apache Camel +description: Use when migrating an existing integration from MuleSoft, BizTalk, Fuse, or older Camel to Apache Camel. user_invocable: false metadata: version: "2.0.0" diff --git a/camel-kit-core/src/main/resources/skills/camel-plan/SKILL.md b/camel-kit-core/src/main/resources/skills/camel-plan/SKILL.md index cb2ea08d..bf6ebc2d 100644 --- a/camel-kit-core/src/main/resources/skills/camel-plan/SKILL.md +++ b/camel-kit-core/src/main/resources/skills/camel-plan/SKILL.md @@ -1,6 +1,6 @@ --- name: camel-plan -description: Break a design spec into implementation tasks with wave analysis. +description: Use when an approved design spec needs to be broken into implementation tasks with wave analysis. user_invocable: false --- diff --git a/camel-kit-core/src/main/resources/skills/camel-ship/SKILL.md b/camel-kit-core/src/main/resources/skills/camel-ship/SKILL.md index 85b35cec..723a2ef2 100644 --- a/camel-kit-core/src/main/resources/skills/camel-ship/SKILL.md +++ b/camel-kit-core/src/main/resources/skills/camel-ship/SKILL.md @@ -1,6 +1,6 @@ --- name: camel-ship -description: Delegate the Technology Preview Ship workflow to the registered Camel-Kit CLI command. +description: Use when the user explicitly asks to ship or publish a Camel project via the Technology Preview Ship workflow. user_invocable: false --- diff --git a/camel-kit-core/src/main/resources/skills/camel-start/SKILL.md b/camel-kit-core/src/main/resources/skills/camel-start/SKILL.md index 58797f8e..6ec7cc96 100644 --- a/camel-kit-core/src/main/resources/skills/camel-start/SKILL.md +++ b/camel-kit-core/src/main/resources/skills/camel-start/SKILL.md @@ -1,6 +1,6 @@ --- name: camel-start -description: Route to the right camel-kit skill for integration work. +description: Use when the user wants to start an integration task and you need to pick the right camel-kit skill. user_invocable: true --- diff --git a/camel-kit-core/src/main/resources/skills/camel-validate/SKILL.md b/camel-kit-core/src/main/resources/skills/camel-validate/SKILL.md index 5a35f727..805d530a 100644 --- a/camel-kit-core/src/main/resources/skills/camel-validate/SKILL.md +++ b/camel-kit-core/src/main/resources/skills/camel-validate/SKILL.md @@ -1,6 +1,6 @@ --- name: camel-validate -description: Static quality analysis of Camel routes — correctness, security, anti-patterns. +description: Use when running static quality analysis on Camel routes for correctness, security, and anti-patterns. user_invocable: false --- diff --git a/camel-kit-core/src/main/resources/templates/claude/camel-ship.md b/camel-kit-core/src/main/resources/templates/claude/camel-ship.md index fe3b14bb..0c9bbc23 100644 --- a/camel-kit-core/src/main/resources/templates/claude/camel-ship.md +++ b/camel-kit-core/src/main/resources/templates/claude/camel-ship.md @@ -1,6 +1,6 @@ --- name: camel-ship -description: Run the Camel Ship Technology Preview through Claude Code native subagents when the active session permits it. +description: Use when the user explicitly asks to ship or publish a Camel project via the Technology Preview Ship workflow. argument-hint: "[ship-options]" user-invocable: true --- diff --git a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/config/AgentRegistryTest.java b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/config/AgentRegistryTest.java index db3f1b5d..e1e49c77 100644 --- a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/config/AgentRegistryTest.java +++ b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/config/AgentRegistryTest.java @@ -29,7 +29,7 @@ class AgentRegistryTest { AgentConfig claude = AgentRegistry.get("claude"); assertNotNull(claude); assertEquals("Claude Code", claude.name()); - assertEquals(".claude/commands", claude.folder()); + assertEquals(".claude/skills", claude.folder()); assertEquals("md", claude.fileFormat()); assertEquals("$ARGUMENTS", claude.argPlaceholder()); assertEquals(".mcp.json", claude.mcpConfigPath()); diff --git a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/ClaudeGeneratorTest.java b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/ClaudeGeneratorTest.java index 5009a10e..054e4506 100644 --- a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/ClaudeGeneratorTest.java +++ b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/ClaudeGeneratorTest.java @@ -2,6 +2,7 @@ package io.github.luigidemasi.camelkit.generator; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Set; import io.github.luigidemasi.camelkit.config.AgentConfig; import io.github.luigidemasi.camelkit.config.AgentRegistry; @@ -14,6 +15,12 @@ import static org.junit.jupiter.api.Assertions.*; class ClaudeGeneratorTest { + private static final Set<String> INTERNAL_SKILLS = Set.of( + "camel-design", + "camel-implement", + "camel-test", + "camel-verify"); + @TempDir Path tempDir; @@ -81,8 +88,6 @@ class ClaudeGeneratorTest { assertTrue(worker.contains("name: camel-ship-worker")); assertTrue(worker.contains("tools: Read, Grep, Glob")); assertFalse(worker.contains("mcpServers")); - String command = Files.readString(ctx.commandsDir().resolve("camel-ship.md")); - assertTrue(command.contains("Read .claude/skills/camel-ship/SKILL.md and follow those instructions")); String claudeMd = Files.readString(tempDir.resolve("CLAUDE.md")); assertTrue(claudeMd.contains("@AGENTS.md"), "CLAUDE.md must import AGENTS.md via @AGENTS.md directive"); @@ -102,7 +107,54 @@ class ClaudeGeneratorTest { String content = Files.readString(brainstormSkill); assertTrue(content.contains("Dispatch")); - // Slash commands still exist - assertTrue(Files.exists(ctx.commandsDir().resolve("camel-migrate.md"))); + // Claude skills are discoverable natively — no duplicate command stubs + assertFalse(Files.exists(ctx.commandsDir().resolve("camel-migrate.md")), + "Claude must not generate command stubs — skills are discoverable via frontmatter"); + } + + @Test + void marksInternalSkillsAsNotUserOrModelInvocableForClaude() throws Exception { + InitContext ctx = createContext(); + new ClaudeGenerator().generate(ctx); + + for (String skillName : INTERNAL_SKILLS) { + String skill = Files.readString(ctx.skillsDir().resolve(skillName + "/SKILL.md")); + assertTrue(skill.contains("user-invocable: false"), + skillName + " must declare user-invocable: false for Claude"); + assertTrue(skill.contains("disable-model-invocation: true"), + skillName + " must not be model invocable for Claude"); + } + + String start = Files.readString(ctx.skillsDir().resolve("camel-start/SKILL.md")); + assertFalse(start.contains("disable-model-invocation: true"), + "camel-start must remain model-invocable"); + } + + @Test + void addsHyphenatedUserInvocableMetadataForClaude() throws Exception { + InitContext ctx = createContext(); + new ClaudeGenerator().generate(ctx); + + String start = Files.readString(ctx.skillsDir().resolve("camel-start/SKILL.md")); + assertTrue(start.contains("user-invocable: true"), + "camel-start must have hyphenated user-invocable for Claude Code discovery"); + + String brainstorm = Files.readString(ctx.skillsDir().resolve("camel-brainstorm/SKILL.md")); + assertTrue(brainstorm.contains("user-invocable: false"), + "camel-brainstorm must have hyphenated user-invocable: false for Claude Code"); + } + + @Test + void skillDescriptionsAreClaudeCodeDiscoverable() throws Exception { + InitContext ctx = createContext(); + new ClaudeGenerator().generate(ctx); + + for (String skillName : Set.of("camel-start", "camel-brainstorm", "camel-migrate", + "camel-plan", "camel-execute", "camel-validate", "camel-ship", + "camel-knowledge", "camel-debug")) { + String skill = Files.readString(ctx.skillsDir().resolve(skillName + "/SKILL.md")); + assertTrue(skill.contains("description: Use when"), + skillName + " description must start with 'Use when' for Claude Code discovery"); + } } } diff --git a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/ShippedAssetStructureTest.java b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/ShippedAssetStructureTest.java index 09fdfc01..1859d4f6 100644 --- a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/ShippedAssetStructureTest.java +++ b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/ShippedAssetStructureTest.java @@ -45,6 +45,7 @@ class ShippedAssetStructureTest { // Command directories that non-stub agents historically used; generation must never recreate them. private static final Map<String, String> LEGACY_COMMAND_DIRS = Map.of( "antigravity", ".agents/commands", + "claude", ".claude/commands", "codex", ".codex/commands", "copilot", ".github/commands"); @@ -800,8 +801,6 @@ class ShippedAssetStructureTest { assertTrue(skillContent.contains("Leave `model` and `isolation` unset")); assertTrue(skillContent.contains("outside the project directory")); assertFalse(skillContent.contains("`fork_context`")); - Path command = ctx.commandsDir().resolve("camel-ship.md"); - assertTrue(Files.readString(command).contains("Read .claude/skills/camel-ship/SKILL.md")); String worker = Files.readString(ctx.projectDir().resolve(".claude/agents/camel-ship-worker.md")); assertTrue(worker.contains("\ntools: Read, Grep, Glob\n")); assertFalse(worker.contains("mcpServers"));
