From 5e11f65865865cbfae662acd69d90e701bf082dd Mon Sep 17 00:00:00 2001 From: Cristina Poncela Cubeiro <140309543+cristinaponcela@users.noreply.github.com> Date: Mon, 17 Aug 2026 19:59:55 +0200 Subject: [PATCH] fix(coding-agent): load nested markdown skills (#8255) --- packages/coding-agent/docs/skills.md | 2 +- packages/coding-agent/src/core/package-manager.ts | 7 ++++++- packages/coding-agent/test/package-manager.test.ts | 10 +++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/coding-agent/docs/skills.md b/packages/coding-agent/docs/skills.md index 198a6c17c..8905b7ff9 100644 --- a/packages/coding-agent/docs/skills.md +++ b/packages/coding-agent/docs/skills.md @@ -36,7 +36,7 @@ Pi loads skills from: Discovery rules: - In `~/.pi/agent/skills/` and `.pi/skills/`, direct root `.md` files are discovered as individual skills when they have valid skill frontmatter with a non-empty `description` - In all skill locations, directories containing `SKILL.md` are discovered recursively -- In `~/.agents/skills/` and project `.agents/skills/`, root `.md` files are ignored +- In `~/.agents/skills/` and project `.agents/skills/`, root `.md` files are ignored, but nested `.md` files in grouping folders are discovered when they declare skill frontmatter - Root Markdown files other than `SKILL.md` that do not look like skills are ignored silently Disable discovery with `--no-skills` (explicit `--skill` paths still load). diff --git a/packages/coding-agent/src/core/package-manager.ts b/packages/coding-agent/src/core/package-manager.ts index c2e9e73d5..f0f98359c 100644 --- a/packages/coding-agent/src/core/package-manager.ts +++ b/packages/coding-agent/src/core/package-manager.ts @@ -397,7 +397,12 @@ function collectSkillEntries( } const relPath = toPosixPath(relative(root, fullPath)); - if (mode === "pi" && dir === root && isFile && entry.name.endsWith(".md") && !ig.ignores(relPath)) { + const shouldIncludeMarkdownFile = + isFile && + entry.name.endsWith(".md") && + !ig.ignores(relPath) && + ((mode === "pi" && dir === root) || (mode === "agents" && dir !== root)); + if (shouldIncludeMarkdownFile) { entries.push(fullPath); continue; } diff --git a/packages/coding-agent/test/package-manager.test.ts b/packages/coding-agent/test/package-manager.test.ts index 86303aab9..2de47fcf0 100644 --- a/packages/coding-agent/test/package-manager.test.ts +++ b/packages/coding-agent/test/package-manager.test.ts @@ -440,13 +440,19 @@ Content`, expect(result.skills.some((r) => r.path === middleSkill && r.enabled)).toBe(true); }); - it("should ignore root markdown files in .agents/skills", async () => { + it("should ignore root markdown files in .agents/skills but discover nested markdown skills", async () => { const agentsSkillsDir = join(tempDir, ".agents", "skills"); mkdirSync(join(agentsSkillsDir, "nested-skill"), { recursive: true }); + mkdirSync(join(agentsSkillsDir, "third-party"), { recursive: true }); + mkdirSync(join(agentsSkillsDir, "third-party", "vendor", "pack"), { recursive: true }); const rootSkill = join(agentsSkillsDir, "root-file.md"); const nestedSkill = join(agentsSkillsDir, "nested-skill", "SKILL.md"); + const nestedMarkdownSkill = join(agentsSkillsDir, "third-party", "child-skill.md"); + const deeplyNestedMarkdownSkill = join(agentsSkillsDir, "third-party", "vendor", "pack", "deep-skill.md"); writeFileSync(rootSkill, "---\nname: root-file\ndescription: Root markdown file\n---\n"); writeFileSync(nestedSkill, "---\nname: nested-skill\ndescription: Nested skill\n---\n"); + writeFileSync(nestedMarkdownSkill, "---\nname: child-skill\ndescription: Nested markdown skill\n---\n"); + writeFileSync(deeplyNestedMarkdownSkill, "---\nname: deep-skill\ndescription: Deep markdown skill\n---\n"); const pm = new DefaultPackageManager({ cwd: join(tempDir, "work"), @@ -458,6 +464,8 @@ Content`, const result = await pm.resolve(); expect(result.skills.some((r) => r.path === rootSkill)).toBe(false); expect(result.skills.some((r) => r.path === nestedSkill && r.enabled)).toBe(true); + expect(result.skills.some((r) => r.path === nestedMarkdownSkill && r.enabled)).toBe(true); + expect(result.skills.some((r) => r.path === deeplyNestedMarkdownSkill && r.enabled)).toBe(true); }); it("should keep ~/.agents/skills user-scoped when cwd is under home in a non-git directory", async () => {