diff --git a/mcp-ts/src/aisetup.ts b/mcp-ts/src/aisetup.ts index fffb55a..7df83f8 100644 --- a/mcp-ts/src/aisetup.ts +++ b/mcp-ts/src/aisetup.ts @@ -2,7 +2,8 @@ * AI instruction file generation for Bindery (MCP server). * * Generates CLAUDE.md, .github/copilot-instructions.md, .cursor/rules, - * AGENTS.md, and .claude/skills//SKILL.md from the book's + * AGENTS.md, and skills under .claude/skills//SKILL.md and + * .agents/skills//SKILL.md from the book's * .bindery/settings.json. * * Template content is maintained in bindery-core/src/templates/*.ts @@ -169,6 +170,10 @@ export function setupAiFiles(options: AiSetupOptions): AiSetupResult { break; case 'agents': writeFile(root, 'AGENTS.md', renderTemplate('agents', ctx), overwrite, versionFile, result); + for (const skill of skills) { + const skillMd = path.join('.agents', 'skills', skill, 'SKILL.md'); + writeFile(root, skillMd, renderTemplate(skill, ctx), overwrite, versionFile, result); + } break; } } diff --git a/mcp-ts/test/aisetup.test.ts b/mcp-ts/test/aisetup.test.ts index 2add7ba..4c7c472 100644 --- a/mcp-ts/test/aisetup.test.ts +++ b/mcp-ts/test/aisetup.test.ts @@ -98,4 +98,25 @@ describe('setupAiFiles skill generation', () => { expect(result.skillZipManifest.created).toEqual([]); expect(fs.readFileSync(path.join(root, '.claude', 'skills', 'read-aloud.zip'), 'utf-8')).toBe('legacy zip bytes'); }); + + it('generates requested skill markdown files for agents target under .agents/skills', async () => { + const { setupAiFiles } = await loadAiSetup(); + + const root = makeRoot(); + write(path.join(root, '.bindery', 'settings.json'), JSON.stringify({ + bookTitle: 'Test Book', + storyFolder: 'Story', + languages: [{ code: 'EN', folderName: 'EN' }], + }, null, 2) + '\n'); + + const result = setupAiFiles({ + root, + targets: ['agents'], + skills: ['read-in'], + overwrite: true, + }); + + expect(result.regenerated).toContain('.agents/skills/read-in/SKILL.md'); + expect(fs.existsSync(path.join(root, '.agents', 'skills', 'read-in', 'SKILL.md'))).toBe(true); + }); }); diff --git a/obsidian-plugin/src/ai-setup.ts b/obsidian-plugin/src/ai-setup.ts index 6f58f43..b64d386 100644 --- a/obsidian-plugin/src/ai-setup.ts +++ b/obsidian-plugin/src/ai-setup.ts @@ -2,7 +2,7 @@ * Obsidian-specific AI setup wrapper. * * Generates AI instruction files (CLAUDE.md, copilot-instructions.md, .cursor/rules, AGENTS.md) - * and Claude skill templates from the book's .bindery/settings.json. + * and skill templates from the book's .bindery/settings.json. * * For Obsidian, this is a simpler adaptation since we don't have the VS Code LM API, * so we just generate the files to the filesystem directly. @@ -89,6 +89,10 @@ export function setupAiFiles( break; case 'agents': writeFile(bookRoot, 'AGENTS.md', renderTemplate('agents', ctx), overwrite, result); + for (const skill of skills) { + const skillDir = path.join('.agents', 'skills', skill); + writeFile(bookRoot, path.join(skillDir, 'SKILL.md'), renderTemplate(skill, ctx), overwrite, result); + } break; } } diff --git a/obsidian-plugin/test/ai-setup.test.ts b/obsidian-plugin/test/ai-setup.test.ts index 0b2d095..b2cc236 100644 --- a/obsidian-plugin/test/ai-setup.test.ts +++ b/obsidian-plugin/test/ai-setup.test.ts @@ -85,6 +85,22 @@ describe('AI Setup', () => { expect(fs.existsSync(agentsPath)).toBe(true); }); + it('should create skill markdown files under .agents/skills when agents target requested', async () => { + const settingsPath = path.join(tempRoot, '.bindery', 'settings.json'); + fs.mkdirSync(path.dirname(settingsPath), { recursive: true }); + fs.writeFileSync(settingsPath, JSON.stringify({ + bookTitle: 'Test Book', + }, null, 2), 'utf-8'); + + await setupAiFiles(mockApp, tempRoot, ['agents'], ['read-in', 'review'], false); + + const readInSkillPath = path.join(tempRoot, '.agents', 'skills', 'read-in', 'SKILL.md'); + const reviewSkillPath = path.join(tempRoot, '.agents', 'skills', 'review', 'SKILL.md'); + + expect(fs.existsSync(readInSkillPath)).toBe(true); + expect(fs.existsSync(reviewSkillPath)).toBe(true); + }); + it('should skip existing files when overwrite is false', async () => { const settingsPath = path.join(tempRoot, '.bindery', 'settings.json'); fs.mkdirSync(path.dirname(settingsPath), { recursive: true }); diff --git a/vscode-ext/src/ai-setup.ts b/vscode-ext/src/ai-setup.ts index 1266974..d9b2cec 100644 --- a/vscode-ext/src/ai-setup.ts +++ b/vscode-ext/src/ai-setup.ts @@ -7,7 +7,7 @@ * claude → CLAUDE.md + .claude/skills//SKILL.md * copilot → .github/copilot-instructions.md * cursor → .cursor/rules - * agents → AGENTS.md (OpenAI Agents, Aider, Codex, etc.) + * agents → AGENTS.md + .agents/skills//SKILL.md (OpenAI Agents, Aider, Codex, etc.) * * Templates and TemplateContext live in @bindery/core (single source of truth). */ @@ -92,6 +92,10 @@ export function setupAiFiles(options: AiSetupOptions): AiSetupResult { case 'agents': writeFile(root, 'AGENTS.md', renderTemplate('agents', ctx), overwrite, result); + for (const skill of skills) { + const skillDir = path.join('.agents', 'skills', skill); + writeFile(root, path.join(skillDir, 'SKILL.md'), renderTemplate(skill, ctx), overwrite, result); + } break; } }