Merge pull request #4141 from code-yeongyu/fix/3396-config-skills-paths-discovery

fix: discover skills from host config.skills.paths set by other plugins
This commit is contained in:
YeonGyu-Kim
2026-05-18 13:50:34 +09:00
committed by GitHub
6 changed files with 203 additions and 0 deletions
@@ -122,4 +122,50 @@ describe("applyAgentConfig .agents skills", () => {
expect(discoveredSkills.map(skill => skill.name)).toContain("project-agent-skill")
expect(discoveredSkills.map(skill => skill.name)).toContain("global-agent-skill")
})
test("discovers skills from host config.skills.paths set by other plugins", async () => {
// given - second call to discoverConfigSourceSkills returns host config skills
discoverConfigSourceSkillsSpy
.mockResolvedValueOnce([])
.mockResolvedValueOnce([
{
name: "host-config-skill",
definition: { name: "host-config-skill", template: "host-template" },
scope: "config",
},
])
// when
await applyAgentConfig({
config: {
model: "anthropic/claude-opus-4-6",
agent: {},
skills: { paths: ["/host/skills"] },
},
pluginConfig: createPluginConfig(),
ctx: { directory: "/tmp/project" },
pluginComponents: createPluginComponents(),
})
// then
const discoveredSkills = createBuiltinAgentsSpy.mock.calls[0]?.[6] as Array<{ name: string }>
expect(discoveredSkills.map(skill => skill.name)).toContain("host-config-skill")
})
test("calls discoverConfigSourceSkills twice when host config has skills", async () => {
// when
await applyAgentConfig({
config: {
model: "anthropic/claude-opus-4-6",
agent: {},
skills: { paths: ["/host/skills"] },
},
pluginConfig: createPluginConfig(),
ctx: { directory: "/tmp/project" },
pluginComponents: createPluginComponents(),
})
// then - called twice: once for pluginConfig.skills, once for host config.skills
expect(discoverConfigSourceSkillsSpy).toHaveBeenCalledTimes(2)
})
})
@@ -36,6 +36,7 @@ import {
} from "./agent-override-protection";
import { buildPrometheusAgentConfig } from "./prometheus-agent-config-builder";
import { buildPlanDemoteConfig } from "./plan-model-inheritance";
import { adaptHostSkillConfig } from "../shared/host-skill-config";
type AgentConfigRecord = Record<string, Record<string, unknown> | undefined> & {
build?: Record<string, unknown>;
@@ -62,8 +63,10 @@ export async function applyAgentConfig(params: {
) as typeof params.pluginConfig.disabled_agents;
const includeClaudeSkillsForAwareness = params.pluginConfig.claude_code?.skills ?? true;
const hostSkillConfig = adaptHostSkillConfig(params.config.skills);
const [
discoveredConfigSourceSkills,
discoveredHostConfigSkills,
discoveredUserSkills,
discoveredProjectSkills,
discoveredProjectAgentsSkills,
@@ -75,6 +78,10 @@ export async function applyAgentConfig(params: {
config: params.pluginConfig.skills,
configDir: params.ctx.directory,
}),
discoverConfigSourceSkills({
config: hostSkillConfig,
configDir: params.ctx.directory,
}),
includeClaudeSkillsForAwareness ? discoverUserClaudeSkills() : Promise.resolve([]),
includeClaudeSkillsForAwareness
? discoverProjectClaudeSkills(params.ctx.directory)
@@ -89,6 +96,7 @@ export async function applyAgentConfig(params: {
const allDiscoveredSkills = [
...discoveredConfigSourceSkills,
...discoveredHostConfigSkills,
...discoveredOpencodeProjectSkills,
...discoveredProjectSkills,
...discoveredProjectAgentsSkills,
@@ -157,4 +157,37 @@ describe("applyCommandConfig", () => {
const commandConfig = config.command as Record<string, { agent?: string }>;
expect(commandConfig["start-work"]?.agent).toBe(getAgentListDisplayName("atlas"));
});
test("includes host config skills declared in config.skills.paths by other plugins", async () => {
// given - second call to discoverConfigSourceSkills returns host config skills
discoverConfigSourceSkillsSpy
.mockResolvedValueOnce([])
.mockResolvedValueOnce([
{
name: "host-config-skill",
definition: {
name: "host-config-skill",
description: "Host config skill",
template: "template",
},
scope: "config",
},
]);
const config: Record<string, unknown> = {
command: {},
skills: { paths: ["/host/skills"] },
};
// when
await applyCommandConfig({
config,
pluginConfig: createPluginConfig(),
ctx: { directory: "/tmp" },
pluginComponents: createPluginComponents(),
});
// then
const commandConfig = config.command as Record<string, { description?: string }>;
expect(commandConfig["host-config-skill"]?.description).toContain("Host config skill");
});
});
@@ -26,6 +26,7 @@ import {
log,
} from "../shared";
import type { PluginComponents } from "./plugin-components-loader";
import { adaptHostSkillConfig } from "../shared/host-skill-config";
export async function applyCommandConfig(params: {
config: Record<string, unknown>;
@@ -47,8 +48,10 @@ export async function applyCommandConfig(params: {
log(getSkillPluginConflictWarning(externalSkillPlugin.pluginName));
}
const hostSkillConfig = adaptHostSkillConfig(params.config.skills);
const [
configSourceSkills,
hostConfigSkills,
userCommands,
projectCommands,
opencodeGlobalCommands,
@@ -64,6 +67,10 @@ export async function applyCommandConfig(params: {
config: params.pluginConfig.skills,
configDir: params.ctx.directory,
}),
discoverConfigSourceSkills({
config: hostSkillConfig,
configDir: params.ctx.directory,
}),
includeClaudeCommands ? loadUserCommands() : Promise.resolve({}),
includeClaudeCommands ? loadProjectCommands(params.ctx.directory) : Promise.resolve({}),
loadOpencodeGlobalCommands(),
@@ -79,6 +86,7 @@ export async function applyCommandConfig(params: {
params.config.command = {
...builtinCommands,
...skillsToCommandDefinitionRecord(configSourceSkills),
...skillsToCommandDefinitionRecord(hostConfigSkills),
...userCommands,
...userSkills,
...globalAgentsSkills,