Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/skip-plugin-document-skills.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Prevent plugin documentation files from appearing as skills.
15 changes: 15 additions & 0 deletions packages/agent-core/src/skill/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ const PROJECT_GENERIC_DIRS = ['.agents/skills'] as const;
// loop forever. Real skill trees are 1-3 levels deep.
const MAX_SKILL_SCAN_DEPTH = 8;

// Plugin packages commonly keep release and repository documentation next to
// their skill entrypoints. These files are not skills, even though legacy flat
// skill discovery accepts arbitrary top-level Markdown files without
// frontmatter.
const PLUGIN_DOCUMENT_FILENAMES = new Set([
'changelog.md',
'code_of_conduct.md',
'contributing.md',
'license.md',
'readme.md',
]);

export interface SkillPathContext {
readonly userHomeDir: string;
/**
Expand Down Expand Up @@ -215,6 +227,9 @@ export async function discoverSkills(
for (const entry of entries) {
if (!entry.endsWith('.md')) continue;
if (entry === 'SKILL.md') continue;
if (root.plugin !== undefined && PLUGIN_DOCUMENT_FILENAMES.has(entry.toLowerCase())) {
continue;
}
const skillName = entry.slice(0, -'.md'.length);
if (directorySkills.has(skillName)) {
warn(
Expand Down
18 changes: 18 additions & 0 deletions packages/agent-core/test/skill/scanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@ describe('skill discovery', () => {
expect(warnings.some((message) => message.includes('Ignoring flat skill'))).toBe(true);
});

it('does not register conventional plugin documentation as flat skills', async () => {
const { repoDir } = await makeWorkspace();
const pluginRoot = path.join(repoDir, 'plugin-skills');
await mkdir(pluginRoot, { recursive: true });
await writeFile(path.join(pluginRoot, 'CHANGELOG.md'), '# Changelog');
await writeFile(path.join(pluginRoot, 'README.md'), '# Plugin readme');
await writeFile(
path.join(pluginRoot, 'release-notes.md'),
['---', 'name: release-notes', 'description: Release notes skill', '---'].join('\n'),
);

const skills = await discoverSkills({
roots: [{ path: pluginRoot, source: 'extra', plugin: { id: 'example-plugin' } }],
});

expect(skills.map((skill) => skill.name)).toEqual(['release-notes']);
});

it('keeps flow skills user-visible while excluding them from model invocation', async () => {
const { repoDir } = await makeWorkspace();
const projectRoot = path.join(repoDir, '.kimi-code', 'skills');
Expand Down