test: move template behavior tests to bindery-core

This commit is contained in:
Erik van der Boom 2026-04-28 10:29:38 +02:00
parent 9cedb5b66b
commit fc8f578d03
4 changed files with 482 additions and 456 deletions

View file

@ -0,0 +1,461 @@
import { describe, expect, it } from 'vitest';
import { renderTemplate, type TemplateContext } from '../src/templates';
// ─── Fixtures ─────────────────────────────────────────────────────────────────
function makeCtx(overrides: Partial<TemplateContext> = {}): TemplateContext {
return {
title: 'Test Book',
author: 'Jane Doe',
description: 'A tale of adventure.',
genre: 'Fantasy',
audience: 'middle-grade',
storyFolder: 'Story',
notesFolder: 'Notes',
arcFolder: 'Arc',
memoriesFolder: '.bindery/memories',
languages: [{ code: 'EN', folderName: 'EN' }],
langList: 'EN (source)',
hasMultiLang: false,
...overrides,
};
}
function makeMultiLangCtx(): TemplateContext {
return makeCtx({
languages: [{ code: 'EN', folderName: 'EN' }, { code: 'NL', folderName: 'NL' }],
langList: 'EN (source), NL (translation)',
hasMultiLang: true,
});
}
function makeMinimalCtx(): TemplateContext {
return {
title: 'Untitled',
author: '',
description: '',
genre: '',
audience: '',
storyFolder: 'Story',
notesFolder: 'Notes',
arcFolder: 'Arc',
memoriesFolder: '.bindery/memories',
languages: [],
langList: 'EN (source)',
hasMultiLang: false,
};
}
// ─── Top-level targets ────────────────────────────────────────────────────────
describe('renderTemplate — claude', () => {
it('contains required sections', () => {
const result = renderTemplate('claude', makeCtx());
expect(result).toContain('# Claude — Test Book');
expect(result).toContain('## Project');
expect(result).toContain('## Start of session');
expect(result).toContain('## Memory system');
expect(result).toContain('## Repo layout');
expect(result).toContain('## Writing rules');
expect(result).toContain('## Available skills');
expect(result).toContain('## MCP server (bindery-mcp)');
});
it('includes genre, description, audience, and author', () => {
const result = renderTemplate('claude', makeCtx());
expect(result).toContain('Fantasy');
expect(result).toContain('A tale of adventure.');
expect(result).toContain('middle-grade');
expect(result).toContain('Jane Doe');
});
it('omits optional fields when empty', () => {
const result = renderTemplate('claude', makeMinimalCtx());
expect(result).not.toContain('Genre:');
expect(result).not.toContain('Author:');
expect(result).not.toContain('Target audience:');
});
it('includes language section for multi-language projects', () => {
const result = renderTemplate('claude', makeMultiLangCtx());
expect(result).toContain('Languages:');
expect(result).toContain('EN (source)');
expect(result).toContain('NL (translation)');
});
it('includes story and arc folder paths', () => {
const result = renderTemplate('claude', makeCtx());
expect(result).toContain('Arc/');
expect(result).toContain('Notes/');
expect(result).toContain('Story/');
});
it('includes audience-specific writing rule when audience is set', () => {
const result = renderTemplate('claude', makeCtx());
expect(result).toContain('middle-grade');
});
it('ends with a newline', () => {
expect(renderTemplate('claude', makeCtx())).toMatch(/\n$/);
});
});
describe('renderTemplate — copilot', () => {
it('contains required sections', () => {
const result = renderTemplate('copilot', makeCtx());
expect(result).toContain('# GitHub Copilot — Test Book');
expect(result).toContain('## Repo layout');
expect(result).toContain('## Writing guidelines');
});
it('includes genre and description when set', () => {
const result = renderTemplate('copilot', makeCtx());
expect(result).toContain('Fantasy');
expect(result).toContain('A tale of adventure.');
});
it('omits project section entirely when all optional fields are empty', () => {
const result = renderTemplate('copilot', makeMinimalCtx());
expect(result).not.toContain('## Project');
});
it('includes audience writing guideline when audience is set', () => {
const result = renderTemplate('copilot', makeCtx());
expect(result).toContain('middle-grade');
});
it('mentions shared workspace skills from the .claude folder', () => {
const result = renderTemplate('copilot', makeCtx());
expect(result).toContain('.claude/skills/');
expect(result).toContain('/translation-review');
expect(result).toContain('/read-in');
});
it('ends with a newline', () => {
expect(renderTemplate('copilot', makeCtx())).toMatch(/\n$/);
});
});
describe('renderTemplate — cursor', () => {
it('contains required sections', () => {
const result = renderTemplate('cursor', makeCtx());
expect(result).toContain('# Cursor rules — Test Book');
expect(result).toContain('## Context files to read');
expect(result).toContain('## Rules');
});
it('references memory and arc folder paths', () => {
const result = renderTemplate('cursor', makeCtx());
expect(result).toContain('.bindery/memories');
expect(result).toContain('Arc/');
});
it('includes audience rule when audience is set', () => {
const result = renderTemplate('cursor', makeCtx());
expect(result).toContain('middle-grade');
});
it('mentions shared workspace skills from the .claude folder', () => {
const result = renderTemplate('cursor', makeCtx());
expect(result).toContain('.claude/skills/');
expect(result).toContain('/translation-review');
expect(result).toContain('/proof-read');
});
it('ends with a newline', () => {
expect(renderTemplate('cursor', makeCtx())).toMatch(/\n$/);
});
});
describe('renderTemplate — agents', () => {
it('contains required sections', () => {
const result = renderTemplate('agents', makeCtx());
expect(result).toContain('# Agent Instructions — Test Book');
expect(result).toContain('## Project overview');
expect(result).toContain('## Start of session');
expect(result).toContain('## Story files');
expect(result).toContain('## Writing guidelines');
expect(result).toContain('## Key reference files');
});
it('includes genre, description, audience, and author', () => {
const result = renderTemplate('agents', makeCtx());
expect(result).toContain('Fantasy');
expect(result).toContain('A tale of adventure.');
expect(result).toContain('middle-grade');
expect(result).toContain('Jane Doe');
});
it('references memories and arc folders', () => {
const result = renderTemplate('agents', makeCtx());
expect(result).toContain('.bindery/memories');
expect(result).toContain('Arc/');
});
it('mentions shared workspace skills from the .claude folder', () => {
const result = renderTemplate('agents', makeCtx());
expect(result).toContain('.claude/skills/');
expect(result).toContain('/translation-review');
expect(result).toContain('/review');
});
it('ends with a newline', () => {
expect(renderTemplate('agents', makeCtx())).toMatch(/\n$/);
});
});
// ─── Skill templates ──────────────────────────────────────────────────────────
describe('renderTemplate — review skill', () => {
it('contains YAML front-matter, title, trigger, and steps', () => {
const result = renderTemplate('review', makeCtx());
expect(result).toContain('name: review');
expect(result).toContain('# Skill: /review');
expect(result).toContain('## Trigger');
expect(result).toContain('## Steps');
expect(result).toContain('## Tools');
expect(result).toContain('## Rules');
});
it('does not embed book title at generation time', () => {
const result = renderTemplate('review', makeCtx());
expect(result).not.toContain('"Test Book"');
});
it('references the arc and memories folder', () => {
const result = renderTemplate('review', makeCtx());
expect(result).toContain('.bindery/memories');
expect(result).toContain('Arc/');
});
it('includes runtime instruction to read settings', () => {
const result = renderTemplate('review', makeCtx());
expect(result).toContain('.bindery/settings.json');
});
it('offers translation-review when translated files changed', () => {
const result = renderTemplate('review', makeCtx());
expect(result).toContain('/translation-review');
expect(result).toContain('translated chapter files');
});
it('does not embed audience at generation time', () => {
const result = renderTemplate('review', makeMinimalCtx());
expect(result).not.toContain('middle-grade');
});
it('mentions Bindery review markers and autoStage marker consumption', () => {
const result = renderTemplate('review', makeCtx());
expect(result).toContain('Bindery: Review start');
expect(result).toContain('Bindery: Review stop');
expect(result).toContain('removes the marker lines');
});
});
describe('renderTemplate — brainstorm skill', () => {
it('contains YAML front-matter, title, trigger, and steps', () => {
const result = renderTemplate('brainstorm', makeCtx());
expect(result).toContain('name: brainstorm');
expect(result).toContain('# Skill: /brainstorm');
expect(result).toContain('## Trigger');
expect(result).toContain('## Steps');
expect(result).toContain('## Tools');
expect(result).toContain('## Rules');
});
it('does not embed book title at generation time', () => {
const result = renderTemplate('brainstorm', makeCtx());
expect(result).not.toContain('"Test Book"');
});
});
describe('renderTemplate — memory skill', () => {
it('contains YAML front-matter, title, trigger, tools, and steps', () => {
const result = renderTemplate('memory', makeCtx());
expect(result).toContain('name: memory');
expect(result).toContain('# Skill: /memory');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Steps');
expect(result).toContain('## Rules');
});
it('references memory_append and memory_compact tools', () => {
const result = renderTemplate('memory', makeCtx());
expect(result).toContain('memory_append');
expect(result).toContain('memory_compact');
expect(result).toContain('memory_list');
});
});
describe('renderTemplate — translate skill', () => {
it('contains YAML front-matter, title, trigger, tools, and steps', () => {
const result = renderTemplate('translate', makeCtx());
expect(result).toContain('name: translate');
expect(result).toContain('# Skill: /translate');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Steps');
expect(result).toContain('## Rules');
});
it('references get_translation and add_translation tools', () => {
const result = renderTemplate('translate', makeCtx());
expect(result).toContain('get_translation');
expect(result).toContain('add_translation');
});
});
describe('renderTemplate — translation-review skill', () => {
it('contains YAML front-matter, title, trigger, tools, and handoff rules', () => {
const result = renderTemplate('translation-review', makeCtx());
expect(result).toContain('name: translation-review');
expect(result).toContain('# Skill: /translation-review');
expect(result).toContain('## Trigger');
expect(result).toContain('## Not this skill');
expect(result).toContain('## Tools');
expect(result).toContain('## Cross-skill handoff');
expect(result).toContain('## Rules');
});
it('references diff review, glossary loading, and source review handoff', () => {
const result = renderTemplate('translation-review', makeCtx());
expect(result).toContain('get_review_text');
expect(result).toContain('get_translation');
expect(result).toContain('/review');
expect(result).toContain('line parity');
});
it('uses agent-agnostic context wording and glossary persistence tools', () => {
const result = renderTemplate('translation-review', makeCtx());
expect(result).toContain('recent conversation');
expect(result).toContain('ask the user if ambiguous');
expect(result).toContain('search(query, targetLanguage)');
expect(result).toContain('add_translation');
expect(result).toContain('call `add_translation` before moving on');
});
it('mentions review markers and the two-section response shape', () => {
const result = renderTemplate('translation-review', makeCtx());
expect(result).toContain('Bindery: Review start');
expect(result).toContain('# Git diff');
expect(result).toContain('# Review markers');
});
});
describe('renderTemplate — status skill', () => {
it('contains YAML front-matter, title, trigger, tools, and steps', () => {
const result = renderTemplate('status', makeCtx());
expect(result).toContain('name: status');
expect(result).toContain('# Skill: /status');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Steps');
});
it('references chapter_status_get and get_overview tools', () => {
const result = renderTemplate('status', makeCtx());
expect(result).toContain('chapter_status_get');
expect(result).toContain('get_overview');
});
});
describe('renderTemplate — continuity skill', () => {
it('contains YAML front-matter, title, trigger, tools, steps, and output format', () => {
const result = renderTemplate('continuity', makeCtx());
expect(result).toContain('name: continuity');
expect(result).toContain('# Skill: /continuity');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Steps');
expect(result).toContain('## Output format');
expect(result).toContain('## Rules');
});
it('references search and get_notes tools', () => {
const result = renderTemplate('continuity', makeCtx());
expect(result).toContain('search');
expect(result).toContain('get_notes');
});
});
describe('renderTemplate — read-aloud skill', () => {
it('contains YAML front-matter, title, trigger, tools, and rules', () => {
const result = renderTemplate('read-aloud', makeCtx());
expect(result).toContain('name: read-aloud');
expect(result).toContain('# Skill: /read-aloud');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Rules');
});
it('includes runtime context instruction to read settings', () => {
const result = renderTemplate('read-aloud', makeCtx());
expect(result).toContain('.bindery/settings.json');
});
it('does not embed audience at generation time', () => {
const result = renderTemplate('read-aloud', makeMinimalCtx());
expect(result).not.toContain('middle-grade');
});
});
describe('renderTemplate — read-in skill', () => {
it('contains YAML front-matter, title, trigger, tools, steps, and rules', () => {
const result = renderTemplate('read-in', makeCtx());
expect(result).toContain('name: read-in');
expect(result).toContain('# Skill: /read-in');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Steps');
expect(result).toContain('## Rules');
});
it('references memory_list, chapter_status_get, and get_overview tools', () => {
const result = renderTemplate('read-in', makeCtx());
expect(result).toContain('memory_list');
expect(result).toContain('chapter_status_get');
expect(result).toContain('get_overview');
});
it('references the memories folder', () => {
const result = renderTemplate('read-in', makeCtx());
expect(result).toContain('.bindery/memories');
});
});
// ─── .bindery/README.md (capabilities reference) ─────────────────────────────
describe('renderTemplate — bindery-readme', () => {
it('includes the workspace title and key capability sections', () => {
const result = renderTemplate('bindery-readme', makeCtx());
expect(result).toContain('Test Book');
expect(result).toContain('## VS Code commands');
expect(result).toContain('## MCP tools');
expect(result).toContain('## Skill workflows');
expect(result).toContain('## Review markers');
});
it('documents review marker syntax and consumption behavior', () => {
const result = renderTemplate('bindery-readme', makeCtx());
expect(result).toContain('<!-- Bindery: Review start -->');
expect(result).toContain('<!-- Bindery: Review stop -->');
expect(result).toContain('removes');
});
it('lists the new VS Code marker commands', () => {
const result = renderTemplate('bindery-readme', makeCtx());
expect(result).toContain('Insert Review Start Marker');
expect(result).toContain('Insert Review Stop Marker');
});
});
// ─── Unknown template ─────────────────────────────────────────────────────────
describe('renderTemplate — unknown', () => {
it('returns an "Unknown template" message for unrecognised names', () => {
const result = renderTemplate('nonexistent', makeCtx());
expect(result).toContain('Unknown template: nonexistent');
});
});

View file

@ -9,8 +9,6 @@ export default defineConfig({
reportsDirectory: './coverage',
include: ['src/**/*.ts'],
exclude: [
'src/templates.ts', // large code-as-data aggregator; covered by mcp-ts tests
'src/templates/**', // individual template modules; covered by mcp-ts tests
'src/index.ts', // barrel re-export only
],
thresholds: {

View file

@ -1,461 +1,29 @@
import { describe, expect, it } from 'vitest';
import { renderTemplate, type TemplateContext } from '../src/templates';
import { renderTemplate as renderFromCore } from '@bindery/core';
import { renderTemplate as renderFromShim } from '../src/templates';
// ─── Fixtures ─────────────────────────────────────────────────────────────────
function makeCtx(overrides: Partial<TemplateContext> = {}): TemplateContext {
return {
title: 'Test Book',
author: 'Jane Doe',
description: 'A tale of adventure.',
genre: 'Fantasy',
audience: 'middle-grade',
storyFolder: 'Story',
notesFolder: 'Notes',
arcFolder: 'Arc',
memoriesFolder: '.bindery/memories',
languages: [{ code: 'EN', folderName: 'EN' }],
langList: 'EN (source)',
hasMultiLang: false,
...overrides,
};
}
function makeMultiLangCtx(): TemplateContext {
return makeCtx({
languages: [{ code: 'EN', folderName: 'EN' }, { code: 'NL', folderName: 'NL' }],
langList: 'EN (source), NL (translation)',
hasMultiLang: true,
});
}
function makeMinimalCtx(): TemplateContext {
return {
title: 'Untitled',
author: '',
description: '',
genre: '',
audience: '',
storyFolder: 'Story',
notesFolder: 'Notes',
arcFolder: 'Arc',
memoriesFolder: '.bindery/memories',
languages: [],
langList: 'EN (source)',
hasMultiLang: false,
};
}
// ─── Top-level targets ────────────────────────────────────────────────────────
describe('renderTemplate — claude', () => {
it('contains required sections', () => {
const result = renderTemplate('claude', makeCtx());
expect(result).toContain('# Claude — Test Book');
expect(result).toContain('## Project');
expect(result).toContain('## Start of session');
expect(result).toContain('## Memory system');
expect(result).toContain('## Repo layout');
expect(result).toContain('## Writing rules');
expect(result).toContain('## Available skills');
expect(result).toContain('## MCP server (bindery-mcp)');
describe('templates shim', () => {
it('re-exports renderTemplate from @bindery/core', () => {
expect(renderFromShim).toBe(renderFromCore);
});
it('includes genre, description, audience, and author', () => {
const result = renderTemplate('claude', makeCtx());
expect(result).toContain('Fantasy');
expect(result).toContain('A tale of adventure.');
expect(result).toContain('middle-grade');
expect(result).toContain('Jane Doe');
});
it('renders templates via shim export', () => {
const result = renderFromShim('copilot', {
title: 'Test Book',
author: 'Jane Doe',
description: 'A tale of adventure.',
genre: 'Fantasy',
audience: 'middle-grade',
storyFolder: 'Story',
notesFolder: 'Notes',
arcFolder: 'Arc',
memoriesFolder: '.bindery/memories',
languages: [{ code: 'EN', folderName: 'EN' }],
langList: 'EN (source)',
hasMultiLang: false,
});
it('omits optional fields when empty', () => {
const result = renderTemplate('claude', makeMinimalCtx());
expect(result).not.toContain('Genre:');
expect(result).not.toContain('Author:');
expect(result).not.toContain('Target audience:');
});
it('includes language section for multi-language projects', () => {
const result = renderTemplate('claude', makeMultiLangCtx());
expect(result).toContain('Languages:');
expect(result).toContain('EN (source)');
expect(result).toContain('NL (translation)');
});
it('includes story and arc folder paths', () => {
const result = renderTemplate('claude', makeCtx());
expect(result).toContain('Arc/');
expect(result).toContain('Notes/');
expect(result).toContain('Story/');
});
it('includes audience-specific writing rule when audience is set', () => {
const result = renderTemplate('claude', makeCtx());
expect(result).toContain('middle-grade');
});
it('ends with a newline', () => {
expect(renderTemplate('claude', makeCtx())).toMatch(/\n$/);
});
});
describe('renderTemplate — copilot', () => {
it('contains required sections', () => {
const result = renderTemplate('copilot', makeCtx());
expect(result).toContain('# GitHub Copilot — Test Book');
expect(result).toContain('## Repo layout');
expect(result).toContain('## Writing guidelines');
});
it('includes genre and description when set', () => {
const result = renderTemplate('copilot', makeCtx());
expect(result).toContain('Fantasy');
expect(result).toContain('A tale of adventure.');
});
it('omits project section entirely when all optional fields are empty', () => {
const result = renderTemplate('copilot', makeMinimalCtx());
expect(result).not.toContain('## Project');
});
it('includes audience writing guideline when audience is set', () => {
const result = renderTemplate('copilot', makeCtx());
expect(result).toContain('middle-grade');
});
it('mentions shared workspace skills from the .claude folder', () => {
const result = renderTemplate('copilot', makeCtx());
expect(result).toContain('.claude/skills/');
expect(result).toContain('/translation-review');
expect(result).toContain('/read-in');
});
it('ends with a newline', () => {
expect(renderTemplate('copilot', makeCtx())).toMatch(/\n$/);
});
});
describe('renderTemplate — cursor', () => {
it('contains required sections', () => {
const result = renderTemplate('cursor', makeCtx());
expect(result).toContain('# Cursor rules — Test Book');
expect(result).toContain('## Context files to read');
expect(result).toContain('## Rules');
});
it('references memory and arc folder paths', () => {
const result = renderTemplate('cursor', makeCtx());
expect(result).toContain('.bindery/memories');
expect(result).toContain('Arc/');
});
it('includes audience rule when audience is set', () => {
const result = renderTemplate('cursor', makeCtx());
expect(result).toContain('middle-grade');
});
it('mentions shared workspace skills from the .claude folder', () => {
const result = renderTemplate('cursor', makeCtx());
expect(result).toContain('.claude/skills/');
expect(result).toContain('/translation-review');
expect(result).toContain('/proof-read');
});
it('ends with a newline', () => {
expect(renderTemplate('cursor', makeCtx())).toMatch(/\n$/);
});
});
describe('renderTemplate — agents', () => {
it('contains required sections', () => {
const result = renderTemplate('agents', makeCtx());
expect(result).toContain('# Agent Instructions — Test Book');
expect(result).toContain('## Project overview');
expect(result).toContain('## Start of session');
expect(result).toContain('## Story files');
expect(result).toContain('## Writing guidelines');
expect(result).toContain('## Key reference files');
});
it('includes genre, description, audience, and author', () => {
const result = renderTemplate('agents', makeCtx());
expect(result).toContain('Fantasy');
expect(result).toContain('A tale of adventure.');
expect(result).toContain('middle-grade');
expect(result).toContain('Jane Doe');
});
it('references memories and arc folders', () => {
const result = renderTemplate('agents', makeCtx());
expect(result).toContain('.bindery/memories');
expect(result).toContain('Arc/');
});
it('mentions shared workspace skills from the .claude folder', () => {
const result = renderTemplate('agents', makeCtx());
expect(result).toContain('.claude/skills/');
expect(result).toContain('/translation-review');
expect(result).toContain('/review');
});
it('ends with a newline', () => {
expect(renderTemplate('agents', makeCtx())).toMatch(/\n$/);
});
});
// ─── Skill templates ──────────────────────────────────────────────────────────
describe('renderTemplate — review skill', () => {
it('contains YAML front-matter, title, trigger, and steps', () => {
const result = renderTemplate('review', makeCtx());
expect(result).toContain('name: review');
expect(result).toContain('# Skill: /review');
expect(result).toContain('## Trigger');
expect(result).toContain('## Steps');
expect(result).toContain('## Tools');
expect(result).toContain('## Rules');
});
it('does not embed book title at generation time', () => {
const result = renderTemplate('review', makeCtx());
expect(result).not.toContain('"Test Book"');
});
it('references the arc and memories folder', () => {
const result = renderTemplate('review', makeCtx());
expect(result).toContain('.bindery/memories');
expect(result).toContain('Arc/');
});
it('includes runtime instruction to read settings', () => {
const result = renderTemplate('review', makeCtx());
expect(result).toContain('.bindery/settings.json');
});
it('offers translation-review when translated files changed', () => {
const result = renderTemplate('review', makeCtx());
expect(result).toContain('/translation-review');
expect(result).toContain('translated chapter files');
});
it('does not embed audience at generation time', () => {
const result = renderTemplate('review', makeMinimalCtx());
expect(result).not.toContain('middle-grade');
});
it('mentions Bindery review markers and autoStage marker consumption', () => {
const result = renderTemplate('review', makeCtx());
expect(result).toContain('Bindery: Review start');
expect(result).toContain('Bindery: Review stop');
expect(result).toContain('removes the marker lines');
});
});
describe('renderTemplate — brainstorm skill', () => {
it('contains YAML front-matter, title, trigger, and steps', () => {
const result = renderTemplate('brainstorm', makeCtx());
expect(result).toContain('name: brainstorm');
expect(result).toContain('# Skill: /brainstorm');
expect(result).toContain('## Trigger');
expect(result).toContain('## Steps');
expect(result).toContain('## Tools');
expect(result).toContain('## Rules');
});
it('does not embed book title at generation time', () => {
const result = renderTemplate('brainstorm', makeCtx());
expect(result).not.toContain('"Test Book"');
});
});
describe('renderTemplate — memory skill', () => {
it('contains YAML front-matter, title, trigger, tools, and steps', () => {
const result = renderTemplate('memory', makeCtx());
expect(result).toContain('name: memory');
expect(result).toContain('# Skill: /memory');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Steps');
expect(result).toContain('## Rules');
});
it('references memory_append and memory_compact tools', () => {
const result = renderTemplate('memory', makeCtx());
expect(result).toContain('memory_append');
expect(result).toContain('memory_compact');
expect(result).toContain('memory_list');
});
});
describe('renderTemplate — translate skill', () => {
it('contains YAML front-matter, title, trigger, tools, and steps', () => {
const result = renderTemplate('translate', makeCtx());
expect(result).toContain('name: translate');
expect(result).toContain('# Skill: /translate');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Steps');
expect(result).toContain('## Rules');
});
it('references get_translation and add_translation tools', () => {
const result = renderTemplate('translate', makeCtx());
expect(result).toContain('get_translation');
expect(result).toContain('add_translation');
});
});
describe('renderTemplate — translation-review skill', () => {
it('contains YAML front-matter, title, trigger, tools, and handoff rules', () => {
const result = renderTemplate('translation-review', makeCtx());
expect(result).toContain('name: translation-review');
expect(result).toContain('# Skill: /translation-review');
expect(result).toContain('## Trigger');
expect(result).toContain('## Not this skill');
expect(result).toContain('## Tools');
expect(result).toContain('## Cross-skill handoff');
expect(result).toContain('## Rules');
});
it('references diff review, glossary loading, and source review handoff', () => {
const result = renderTemplate('translation-review', makeCtx());
expect(result).toContain('get_review_text');
expect(result).toContain('get_translation');
expect(result).toContain('/review');
expect(result).toContain('line parity');
});
it('uses agent-agnostic context wording and glossary persistence tools', () => {
const result = renderTemplate('translation-review', makeCtx());
expect(result).toContain('recent conversation');
expect(result).toContain('ask the user if ambiguous');
expect(result).toContain('search(query, targetLanguage)');
expect(result).toContain('add_translation');
expect(result).toContain('call `add_translation` before moving on');
});
it('mentions review markers and the two-section response shape', () => {
const result = renderTemplate('translation-review', makeCtx());
expect(result).toContain('Bindery: Review start');
expect(result).toContain('# Git diff');
expect(result).toContain('# Review markers');
});
});
describe('renderTemplate — status skill', () => {
it('contains YAML front-matter, title, trigger, tools, and steps', () => {
const result = renderTemplate('status', makeCtx());
expect(result).toContain('name: status');
expect(result).toContain('# Skill: /status');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Steps');
});
it('references chapter_status_get and get_overview tools', () => {
const result = renderTemplate('status', makeCtx());
expect(result).toContain('chapter_status_get');
expect(result).toContain('get_overview');
});
});
describe('renderTemplate — continuity skill', () => {
it('contains YAML front-matter, title, trigger, tools, steps, and output format', () => {
const result = renderTemplate('continuity', makeCtx());
expect(result).toContain('name: continuity');
expect(result).toContain('# Skill: /continuity');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Steps');
expect(result).toContain('## Output format');
expect(result).toContain('## Rules');
});
it('references search and get_notes tools', () => {
const result = renderTemplate('continuity', makeCtx());
expect(result).toContain('search');
expect(result).toContain('get_notes');
});
});
describe('renderTemplate — read-aloud skill', () => {
it('contains YAML front-matter, title, trigger, tools, and rules', () => {
const result = renderTemplate('read-aloud', makeCtx());
expect(result).toContain('name: read-aloud');
expect(result).toContain('# Skill: /read-aloud');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Rules');
});
it('includes runtime context instruction to read settings', () => {
const result = renderTemplate('read-aloud', makeCtx());
expect(result).toContain('.bindery/settings.json');
});
it('does not embed audience at generation time', () => {
const result = renderTemplate('read-aloud', makeMinimalCtx());
expect(result).not.toContain('middle-grade');
});
});
describe('renderTemplate — read-in skill', () => {
it('contains YAML front-matter, title, trigger, tools, steps, and rules', () => {
const result = renderTemplate('read-in', makeCtx());
expect(result).toContain('name: read-in');
expect(result).toContain('# Skill: /read-in');
expect(result).toContain('## Trigger');
expect(result).toContain('## Tools');
expect(result).toContain('## Steps');
expect(result).toContain('## Rules');
});
it('references memory_list, chapter_status_get, and get_overview tools', () => {
const result = renderTemplate('read-in', makeCtx());
expect(result).toContain('memory_list');
expect(result).toContain('chapter_status_get');
expect(result).toContain('get_overview');
});
it('references the memories folder', () => {
const result = renderTemplate('read-in', makeCtx());
expect(result).toContain('.bindery/memories');
});
});
// ─── .bindery/README.md (capabilities reference) ─────────────────────────────
describe('renderTemplate — bindery-readme', () => {
it('includes the workspace title and key capability sections', () => {
const result = renderTemplate('bindery-readme', makeCtx());
expect(result).toContain('Test Book');
expect(result).toContain('## VS Code commands');
expect(result).toContain('## MCP tools');
expect(result).toContain('## Skill workflows');
expect(result).toContain('## Review markers');
});
it('documents review marker syntax and consumption behavior', () => {
const result = renderTemplate('bindery-readme', makeCtx());
expect(result).toContain('<!-- Bindery: Review start -->');
expect(result).toContain('<!-- Bindery: Review stop -->');
expect(result).toContain('removes');
});
it('lists the new VS Code marker commands', () => {
const result = renderTemplate('bindery-readme', makeCtx());
expect(result).toContain('Insert Review Start Marker');
expect(result).toContain('Insert Review Stop Marker');
});
});
// ─── Unknown template ─────────────────────────────────────────────────────────
describe('renderTemplate — unknown', () => {
it('returns an "Unknown template" message for unrecognised names', () => {
const result = renderTemplate('nonexistent', makeCtx());
expect(result).toContain('Unknown template: nonexistent');
});
});
});

View file

@ -9,7 +9,6 @@ export default defineConfig({
reportsDirectory: './coverage',
include: ['src/**/*.ts'],
exclude: [
'src/templates.ts', // large code-as-data blob
'src/index.ts', // server bootstrap, exercised by integration-stdio tests
],
thresholds: {