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
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,11 @@ export async function discoverFileSkills(
}

if (isTopLevel) {
let rootIsBundle = false;
if (root.plugin !== undefined) {
const rootSkillMd = path.join(dirPath, 'SKILL.md');
if (await isFile(rootSkillMd)) {
rootIsBundle = true;
await parseAndRegister({
byDiscoveryKey,
skipped,
Expand All @@ -108,21 +110,23 @@ export async function discoverFileSkills(
}
}

for (const entry of entries) {
if (!entry.endsWith('.md')) continue;
if (entry === 'SKILL.md') continue;
const skillName = entry.slice(0, -'.md'.length);
if (directorySkills.has(skillName)) continue;
const skillMdPath = path.join(dirPath, entry);
if (!(await isFile(skillMdPath))) continue;
await parseAndRegister({
byDiscoveryKey,
skipped,
warn,
skillMdPath,
skillDirName: skillName,
root,
});
if (!rootIsBundle) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply the bundle guard to runtime skill discovery

Workspace/session catalogs are built with RuntimeSkillDiscovery in program.ts, not this FileSkillDiscovery, and its top-level loop at runtimeSkillDiscovery.ts:142-158 still registers every sibling .md file after loading the root SKILL.md. Consequently, the reported plugin layout continues to expose GLOSSARY-FORMAT as a phantom skill during normal session use; mirror this guard in the runtime scanner (and cover that production path) as well.

Useful? React with 👍 / 👎.

for (const entry of entries) {
if (!entry.endsWith('.md')) continue;
if (entry === 'SKILL.md') continue;
const skillName = entry.slice(0, -'.md'.length);
if (directorySkills.has(skillName)) continue;
const skillMdPath = path.join(dirPath, entry);
if (!(await isFile(skillMdPath))) continue;
await parseAndRegister({
byDiscoveryKey,
skipped,
warn,
skillMdPath,
skillDirName: skillName,
root,
});
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ describe('FileSkillDiscovery', () => {
expect(result.skills.map((s) => s.name)).toEqual(['summarize']);
});

it('does not register payload files beside a bundle SKILL.md at a plugin root', async () => {
await writeSkill('teach/SKILL.md', 'name: teach\ndescription: the teach bundle');
await writeFile(join(root, 'teach', 'GLOSSARY-FORMAT.md'), '# payload\n');

const result = await discover([pluginSkillRoot('teach', 'mattpocock-skills')]);

expect(result.skills.map((s) => s.name)).toEqual(['teach']);
});

it('discovers only the root SKILL.md for a root-skill-only plugin root', async () => {
await writeSkill('plugin/SKILL.md', 'name: root-skill\ndescription: at plugin root');
await writeFile(join(root, 'plugin', 'CHANGELOG.md'), '# Changelog\n');
Expand Down
Loading