feat(ai-setup): generate .agents skills for agents target across hosts

- write .agents/skills/<skill>/SKILL.md when agents target is selected

- apply parity updates in MCP, VS Code extension, and Obsidian plugin

- add/extend ai-setup tests for agents skill generation
This commit is contained in:
Erik van der Boom 2026-07-16 00:38:32 +02:00
parent bcfa6817ed
commit 355dea98a8
5 changed files with 53 additions and 3 deletions

View file

@ -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>/SKILL.md from the book's
* AGENTS.md, and skills under .claude/skills/<skill>/SKILL.md and
* .agents/skills/<skill>/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;
}
}

View file

@ -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);
});
});

View file

@ -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;
}
}

View file

@ -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 });

View file

@ -7,7 +7,7 @@
* claude CLAUDE.md + .claude/skills/<skill>/SKILL.md
* copilot .github/copilot-instructions.md
* cursor .cursor/rules
* agents AGENTS.md (OpenAI Agents, Aider, Codex, etc.)
* agents AGENTS.md + .agents/skills/<skill>/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;
}
}