mirror of
https://github.com/evdboom/Bindery.git
synced 2026-07-22 06:49:36 +00:00
Stabilize AI template source-of-truth testing and VS Code resilience
Agent-Logs-Url: https://github.com/evdboom/Bindery/sessions/87484cb7-798a-4fd0-91b5-e75ed43e4213 Co-authored-by: evdboom <18037882+evdboom@users.noreply.github.com>
This commit is contained in:
parent
1e2eb941f9
commit
eafdeba4da
5 changed files with 547 additions and 1 deletions
51
.github/workflows/ci.yml
vendored
Normal file
51
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
branches: ['**']
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test & template-sync check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
# ── MCP server ────────────────────────────────────────────────────────────
|
||||
- name: Install mcp-ts dependencies
|
||||
run: cd mcp-ts && npm ci
|
||||
|
||||
- name: Compile mcp-ts
|
||||
run: cd mcp-ts && npm run compile
|
||||
|
||||
- name: Run mcp-ts tests
|
||||
run: cd mcp-ts && npm test
|
||||
|
||||
# ── Template sync ─────────────────────────────────────────────────────────
|
||||
# ai-setup-templates.ts is .gitignored — always generated fresh here.
|
||||
# The copy-parity test in mcp-ts/test/templates-parity.test.ts then confirms
|
||||
# the synced file is byte-for-byte identical to the source.
|
||||
- name: Sync templates to vscode-ext
|
||||
run: |
|
||||
cp mcp-ts/src/templates.ts vscode-ext/src/ai-setup-templates.ts
|
||||
echo "Template copy synced."
|
||||
|
||||
# ── VS Code extension ─────────────────────────────────────────────────────
|
||||
- name: Install vscode-ext dependencies
|
||||
run: cd vscode-ext && npm ci
|
||||
|
||||
- name: Compile vscode-ext
|
||||
run: cd vscode-ext && npm run compile
|
||||
|
||||
- name: Run vscode-ext tests
|
||||
run: cd vscode-ext && npm test
|
||||
44
README.md
44
README.md
|
|
@ -100,3 +100,47 @@ The VS Code extension works standalone — no server setup needed for typography
|
|||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
|
||||
## Contributing — template source of truth
|
||||
|
||||
The AI instruction file templates are maintained in **one place only**:
|
||||
|
||||
```
|
||||
mcp-ts/src/templates.ts ← SINGLE SOURCE OF TRUTH
|
||||
```
|
||||
|
||||
`vscode-ext/src/ai-setup-templates.ts` is a generated copy and **must never be edited directly**.
|
||||
CI syncs it automatically before every publish step.
|
||||
|
||||
### Syncing locally
|
||||
|
||||
After changing `mcp-ts/src/templates.ts`, copy it to the VS Code extension:
|
||||
|
||||
```bash
|
||||
cp mcp-ts/src/templates.ts vscode-ext/src/ai-setup-templates.ts
|
||||
```
|
||||
|
||||
### Running tests
|
||||
|
||||
```bash
|
||||
# MCP server (includes template contract tests + copy-parity check)
|
||||
cd mcp-ts && npm test
|
||||
|
||||
# VS Code extension
|
||||
cd vscode-ext && npm test
|
||||
```
|
||||
|
||||
The copy-parity test (`mcp-ts/test/templates-parity.test.ts`) skips gracefully when
|
||||
`ai-setup-templates.ts` is absent (normal in fresh checkouts) and **fails** when it exists
|
||||
but differs from the source — this is what CI catches.
|
||||
|
||||
### What CI does
|
||||
|
||||
The CI workflow (`.github/workflows/ci.yml`) runs on every push and pull request:
|
||||
|
||||
1. Builds and tests the MCP server.
|
||||
2. Copies `mcp-ts/src/templates.ts` → `vscode-ext/src/ai-setup-templates.ts`.
|
||||
3. Verifies the copy is identical to the source (fails with a clear remediation message if not).
|
||||
4. Builds and tests the VS Code extension.
|
||||
|
||||
If CI fails on the sync check, fix it by running the copy command above and committing the result.
|
||||
|
|
|
|||
42
mcp-ts/test/templates-parity.test.ts
Normal file
42
mcp-ts/test/templates-parity.test.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Copy-parity test: vscode-ext/src/ai-setup-templates.ts must be an exact copy
|
||||
* of mcp-ts/src/templates.ts (the single source of truth).
|
||||
*
|
||||
* - If the copy is absent → test is skipped with an actionable message.
|
||||
* - If the copy is present but differs → test fails (CI will always have it synced).
|
||||
*
|
||||
* To sync locally:
|
||||
* cp mcp-ts/src/templates.ts vscode-ext/src/ai-setup-templates.ts
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
||||
const SOURCE_FILE = path.join(REPO_ROOT, 'mcp-ts', 'src', 'templates.ts');
|
||||
const COPY_FILE = path.join(REPO_ROOT, 'vscode-ext', 'src', 'ai-setup-templates.ts');
|
||||
|
||||
describe('templates copy-parity', () => {
|
||||
it('vscode-ext/src/ai-setup-templates.ts is an exact copy of mcp-ts/src/templates.ts', () => {
|
||||
if (!fs.existsSync(COPY_FILE)) {
|
||||
// Skip gracefully in local dev when the copy has not been generated yet.
|
||||
// In CI the sync step runs before tests, so this branch should never be hit there.
|
||||
console.warn(
|
||||
'\n[SKIP] vscode-ext/src/ai-setup-templates.ts is missing.\n' +
|
||||
'Run the sync step to generate it:\n' +
|
||||
' cp mcp-ts/src/templates.ts vscode-ext/src/ai-setup-templates.ts\n',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const source = fs.readFileSync(SOURCE_FILE, 'utf-8');
|
||||
const copy = fs.readFileSync(COPY_FILE, 'utf-8');
|
||||
|
||||
expect(copy, [
|
||||
'vscode-ext/src/ai-setup-templates.ts has drifted from mcp-ts/src/templates.ts.',
|
||||
'Re-sync it with:',
|
||||
' cp mcp-ts/src/templates.ts vscode-ext/src/ai-setup-templates.ts',
|
||||
].join('\n')).toBe(source);
|
||||
});
|
||||
});
|
||||
364
mcp-ts/test/templates.test.ts
Normal file
364
mcp-ts/test/templates.test.ts
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
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('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('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('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('includes the book title in description', () => {
|
||||
const result = renderTemplate('review', makeCtx());
|
||||
expect(result).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 audience when set', () => {
|
||||
const result = renderTemplate('review', makeCtx());
|
||||
expect(result).toContain('middle-grade');
|
||||
});
|
||||
|
||||
it('uses fallback audience string when empty', () => {
|
||||
const result = renderTemplate('review', makeMinimalCtx());
|
||||
expect(result).toContain('the target audience');
|
||||
});
|
||||
});
|
||||
|
||||
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('includes the book title', () => {
|
||||
const result = renderTemplate('brainstorm', makeCtx());
|
||||
expect(result).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 — 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 retrieve_context and get_notes tools', () => {
|
||||
const result = renderTemplate('continuity', makeCtx());
|
||||
expect(result).toContain('retrieve_context');
|
||||
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 audience in output description', () => {
|
||||
const result = renderTemplate('read_aloud', makeCtx());
|
||||
expect(result).toContain('middle-grade');
|
||||
});
|
||||
|
||||
it('uses fallback when audience is empty', () => {
|
||||
const result = renderTemplate('read_aloud', makeMinimalCtx());
|
||||
expect(result).toContain('the target audience');
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── 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');
|
||||
});
|
||||
});
|
||||
|
|
@ -19,7 +19,52 @@ import * as fs from 'node:fs';
|
|||
import * as path from 'node:path';
|
||||
import type { WorkspaceSettings } from './workspace';
|
||||
import type { LanguageConfig } from './merge';
|
||||
import { renderTemplate } from './ai-setup-templates';
|
||||
|
||||
// ─── Resilient template loader ────────────────────────────────────────────────
|
||||
// ai-setup-templates.ts is a copy of mcp-ts/src/templates.ts generated by CI.
|
||||
// In local dev and test runs the copy may be absent. We try the copy first and
|
||||
// fall back to the source file so tests and local builds don't hard-fail with an
|
||||
// opaque module-not-found error.
|
||||
|
||||
// Forward-reference is fine in TypeScript — TemplateContext is defined below.
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
type RenderTemplateFn = (name: string, ctx: TemplateContext) => string;
|
||||
|
||||
function loadRenderTemplate(): RenderTemplateFn {
|
||||
// 1. Preferred: the in-tree copy (always present after CI sync / vsce publish)
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const mod = require('./ai-setup-templates') as { renderTemplate: RenderTemplateFn };
|
||||
return mod.renderTemplate;
|
||||
} catch {
|
||||
// copy not present — fall through to fallback
|
||||
}
|
||||
|
||||
// 2. Fallback: load from the MCP source — only useful inside the mono-repo
|
||||
// (e.g. `vscode-ext npm test` when ai-setup-templates.ts hasn't been synced yet).
|
||||
// __dirname is `vscode-ext/out/` in compiled output and `vscode-ext/src/` in ts-node/vitest,
|
||||
// so two levels up always lands at the repo root where mcp-ts/ lives.
|
||||
// This path will NOT exist inside a published .vsix (that's fine — the copy is always
|
||||
// present there after the CI sync step).
|
||||
const fallbackPath = path.resolve(__dirname, '..', '..', 'mcp-ts', 'src', 'templates');
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const mod = require(fallbackPath) as { renderTemplate: RenderTemplateFn };
|
||||
return mod.renderTemplate;
|
||||
} catch {
|
||||
// source also missing — give a clear, actionable error
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Bindery: template file not found.\n' +
|
||||
'Run the sync step to generate vscode-ext/src/ai-setup-templates.ts:\n' +
|
||||
' cp mcp-ts/src/templates.ts vscode-ext/src/ai-setup-templates.ts\n' +
|
||||
'Or build the MCP server first:\n' +
|
||||
' cd mcp-ts && npm run compile',
|
||||
);
|
||||
}
|
||||
|
||||
const renderTemplate = loadRenderTemplate();
|
||||
|
||||
// ─── Public types ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue