Template Rendering - Chris-Cullins/wiki_bot GitHub Wiki
The Template Rendering area provides a flexible, cache-enabled system for loading and interpolating Markdown templates used throughout the wiki generation process. The TemplateRenderer class manages template discovery across multiple directories with variant support, while the loadPrompt function handles LLM prompt template loading with variable substitution.
Key Responsibilities:
- Load Markdown templates from configurable directory hierarchies
- Support template variants (e.g., area-specific templates)
- Cache template contents to minimize filesystem I/O
- Perform placeholder interpolation using
{{variable}}syntax - Provide fallback mechanisms when templates are not found
TemplateRenderer class — The core template engine that handles loading, caching, and rendering Markdown templates.
src/templates/*.md — Default template files containing placeholder-based Markdown skeletons for wiki pages (architecture, area, home).
src/prompt-loader.ts — Standalone utility for loading LLM prompts from the src/prompts/ directory with variable injection.
When constructing a TemplateRenderer, you can optionally provide a custom template directory. The renderer establishes a search hierarchy:
- Custom directory (if provided) — User-specified templates take precedence
-
Default directory (
src/templates/) — Built-in fallback templates
When rendering, the system checks each directory in order, stopping at the first match. This allows users to override default templates without modifying the source.
The render method accepts an options parameter with optional variant and variantSubdir fields. Candidate paths are generated in priority order:
-
{baseDir}/{variantSubdir}/{variant}.md(if both variant and variantSubdir specified) -
{baseDir}/{templateName}-{variant}.md(if variant specified) -
{baseDir}/{variant}.md(if variant specified) -
{baseDir}/{templateName}.md(base template)
For example, rendering the "area" template with variant "configuration-management" and variantSubdir "areas" would check:
{customDir}/areas/configuration-management.md{customDir}/area-configuration-management.md{customDir}/configuration-management.md{customDir}/area.md{defaultDir}/areas/configuration-management.md- ... (continues through default directory)
Templates are cached using composite keys: {templateName}:{variant} (or just {templateName} if no variant). The cache stores:
- String values for successfully loaded templates
-
nullfor templates that don't exist (preventing repeated filesystem checks)
The cache is instance-scoped and persists for the lifetime of the TemplateRenderer object.
The interpolate method performs simple find-and-replace using regular expressions:
- Searches for
{{\s*key\s*}}patterns (whitespace-tolerant) - Replaces with corresponding context values
- Treats missing context values as empty strings
- Escapes special regex characters in keys to prevent injection issues
Initializes the template search directories and cache.
const renderer = new TemplateRenderer('/path/to/custom/templates');Parameters:
-
customDir— Optional absolute path to custom template directory
Behavior:
- Resolves the default template directory relative to the module location
- Uses
uniquePaths()to deduplicate directory entries - Initializes an empty cache
TemplateRenderer.render(templateName: string, context: Record<string, string>, options?: RenderOptions): Promise<string>
Primary method for loading and rendering a template.
const output = await renderer.render('area', {
title: 'Authentication',
content: '# Authentication\n\nHandles user login...',
depth: 'standard'
}, {
variant: 'authentication',
variantSubdir: 'areas'
});Parameters:
-
templateName— Base name of the template (without extension) -
context— Key-value pairs for placeholder substitution -
options.variant— Optional variant identifier for specialized templates -
options.variantSubdir— Optional subdirectory for variant templates
Returns: Rendered template string, or context.content if no template found
Error Handling:
- ENOENT errors (file not found) are silently ignored during candidate probing
- Other filesystem errors trigger console warnings but don't throw
Loads an LLM prompt from src/prompts/{promptName}.md and injects variables.
const prompt = await loadPrompt('generate-area-documentation', {
area: 'Configuration',
fileContentText: '--- config.ts ---\n...'
});Parameters:
-
promptName— Name of the prompt file (without extension) -
variables— Variables to substitute in{{key}}placeholders
Returns: Prompt string with all placeholders replaced
Key Difference from TemplateRenderer:
- No caching (prompts may use dynamic variables)
- No variant support
- Fixed directory (
src/prompts/) - Uses global regex replacement for all occurrences
The escapeRegExp method contains a subtle bug on src/template-renderer.ts:97:
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\{{fileContentText}}');This should be '\\{{content}}' to properly escape matched characters. The current implementation replaces special characters with the literal string \{{fileContentText}}, which will cause incorrect interpolation behavior if context keys contain regex metacharacters.
When no template is found, render() returns context.content ?? ''. This means:
- If you pass
contentin the context, it becomes the fallback - If no
contentkey exists, you get an empty string - The caller in
WikiGeneratorrelies on this fallback for passthrough rendering
There is no cache invalidation mechanism. If templates change on disk during execution, the renderer will continue serving stale cached versions. For long-running processes that need to respond to template changes, consider:
- Creating new
TemplateRendererinstances - Adding a manual cache clearing method
- Implementing filesystem watchers
When generating variants from area names, WikiGenerator.slugify() is used to normalize names:
- Lowercase conversion
- Non-alphanumeric characters replaced with hyphens
- Leading/trailing hyphens stripped
Ensure custom variant templates follow this naming convention for automatic discovery.
The renderer suppresses most errors to support graceful fallback behavior. This aids resilience but can mask configuration issues. When debugging template problems:
- Check console warnings for non-ENOENT errors
- Verify search directories using
renderer.searchDirectories - Add temporary logging in
loadTemplate()to trace candidate path checks
const renderer = new TemplateRenderer();
const homeContent = await renderer.render('home', {
content: '# Home\n\nWelcome to the wiki!'
});const renderer = new TemplateRenderer('/workspace/.wiki-templates');
// Will check /workspace/.wiki-templates/area.md first,
// then fall back to src/templates/area.md
const areaDoc = await renderer.render('area', {
title: 'Data Layer',
content: '# Data Layer\n\n...'
});const renderer = new TemplateRenderer();
// For the "Configuration Management" area
const configDoc = await renderer.render(
'area',
{
title: 'Configuration Management',
content: '# Configuration Management\n\n...',
depth: 'deep'
},
{
variant: 'configuration-management',
variantSubdir: 'areas'
}
);import { loadPrompt } from './prompt-loader.js';
const prompt = await loadPrompt('generate-home-page', {
structureText: repoTree,
repoRoot: '/workspace/my-project'
});
// Use with LLM query...
const query = createQuery(prompt);