Template Renderer
Step 1: Validate Inputs (SECURITY - MANDATORY)
Template Path Validation (SEC-SPEC-002):
-
Verify template file exists within PROJECT_ROOT
-
Reject any path traversal attempts (../)
-
Only allow templates from .claude/templates/
Token Whitelist Validation (SEC-SPEC-003):
// Allowed tokens by template type const SPEC_TOKENS = [ 'FEATURE_NAME', 'VERSION', 'AUTHOR', 'DATE', 'STATUS', 'ACCEPTANCE_CRITERIA_1', 'ACCEPTANCE_CRITERIA_2', 'ACCEPTANCE_CRITERIA_3', 'TERM_1', 'TERM_2', 'TERM_3', 'HTTP_METHOD', 'ENDPOINT_PATH', 'PROJECT_NAME', ];
const PLAN_TOKENS = [ 'PLAN_TITLE', 'DATE', 'FRAMEWORK_VERSION', 'STATUS', 'EXECUTIVE_SUMMARY', 'TOTAL_TASKS', 'FEATURES_COUNT', 'ESTIMATED_TIME', 'STRATEGY', 'KEY_DELIVERABLES_LIST', 'PHASE_N_NAME', 'PHASE_N_PURPOSE', 'PHASE_N_DURATION', 'DEPENDENCIES', 'PARALLEL_OK', 'VERIFICATION_COMMANDS', ];
const TASKS_TOKENS = [ 'FEATURE_NAME', 'VERSION', 'AUTHOR', 'DATE', 'STATUS', 'PRIORITY', 'ESTIMATED_EFFORT', 'RELATED_SPECS', 'DEPENDENCIES', 'FEATURE_DISPLAY_NAME', 'FEATURE_DESCRIPTION', 'BUSINESS_VALUE', 'USER_IMPACT', 'EPIC_NAME', 'EPIC_GOAL', 'SUCCESS_CRITERIA', ];
Token Value Sanitization (SEC-SPEC-004):
function sanitizeTokenValue(value) { return String(value) .replace(/[<>]/g, '') // Prevent HTML injection .replace(/${/g, '') // Prevent template literal injection .replace(/{{/g, '') // Prevent nested token injection .trim(); }
Step 2: Read Template
Read the template file using Read or mcpfilesystemread_text_file:
-
.claude/templates/specification-template.md (46 tokens)
-
.claude/templates/plan-template.md (30+ tokens)
-
.claude/templates/tasks-template.md (20+ tokens)
Step 3: Token Replacement
Replace all {{TOKEN}} placeholders with sanitized values:
function renderTemplate(templateContent, tokenMap) { let rendered = templateContent;
// Replace each token
for (const [token, value] of Object.entries(tokenMap)) {
// Validate token is in whitelist
if (!isAllowedToken(token, templateType)) {
throw new Error(Token not in whitelist: ${token});
}
// Sanitize value
const sanitizedValue = sanitizeTokenValue(value);
// Replace all occurrences
const regex = new RegExp(`\\{\\{${token}\\}\\}`, 'g');
rendered = rendered.replace(regex, sanitizedValue);
}
// Check for missing required tokens
const missingTokens = rendered.match(/{{[A-Z_0-9]+}}/g);
if (missingTokens) {
throw new Error(Missing required tokens: ${missingTokens.join(', ')});
}
return rendered; }
Step 4: Schema Validation (Specification Templates Only)
For specification templates, validate the rendered output against JSON Schema:
// Extract YAML frontmatter const yamlMatch = rendered.match(/^---\n([\s\S]*?)\n---/); if (!yamlMatch) { throw new Error('No YAML frontmatter found'); }
// Parse YAML const yaml = require('js-yaml'); const frontmatter = yaml.load(yamlMatch[1]);
// Validate against schema const schema = JSON.parse( fs.readFileSync('.claude/schemas/specification-template.schema.json', 'utf8') );
const Ajv = require('ajv'); const ajv = new Ajv(); const validate = ajv.compile(schema);
if (!validate(frontmatter)) {
throw new Error(Schema validation failed: ${JSON.stringify(validate.errors)});
}
Step 5: Write Output
Write the rendered template to the output path using Write or mcpfilesystemwrite_file:
-
Verify output path is within PROJECT_ROOT
-
Create parent directories if needed
-
Write file with UTF-8 encoding
Step 6: Verification
Run post-rendering checks:
Check no unresolved tokens remain
grep "{{" <output-file> && echo "ERROR: Unresolved tokens found!" || echo "✓ All tokens resolved"
For specifications: Validate YAML frontmatter
head -50 <output-file> | grep -E "^---$" | wc -l # Should output: 2
For specifications: Validate against schema (if ajv installed)
ajv validate -s .claude/schemas/specification-template.schema.json -d <output-file>
</execution_process>
<best_practices>
-
Always validate template paths: Use PROJECT_ROOT validation before reading
-
Sanitize all token values: Prevent injection attacks (SEC-SPEC-004)
-
Enforce token whitelist: Only allow predefined tokens (SEC-SPEC-003)
-
Error on missing tokens: Don't silently ignore missing required tokens
-
Warn on unused tokens: Help users catch typos in token names
-
Preserve Markdown formatting: Don't alter indentation, bullets, code blocks
-
Validate schema for specs: Run JSON Schema validation for specification templates
-
Log all operations: Record template, tokens used, output path to memory
</best_practices>
<error_handling>
Missing Required Tokens:
ERROR: Missing required tokens in template:
- {{FEATURE_NAME}}
- {{ACCEPTANCE_CRITERIA_1}}
Provide these tokens in the token map.
Invalid Token (Not in Whitelist):
ERROR: Token not in whitelist: INVALID_TOKEN Allowed tokens for specification-template: FEATURE_NAME, VERSION, AUTHOR, DATE, ...
Template Path Traversal:
ERROR: Template path outside PROJECT_ROOT Path: ../../etc/passwd Only templates from .claude/templates/ are allowed.
Schema Validation Failure (Specification Templates):
ERROR: Schema validation failed:
- /version: must match pattern "^\d+.\d+.\d+$"
- /acceptance_criteria: must have at least 1 item
Unused Tokens Warning:
WARNING: Unused tokens provided:
- EXTRA_TOKEN_1
- EXTRA_TOKEN_2
These tokens are not in the template. Check for typos.
</error_handling>
// From another skill (e.g., spec-gathering) Skill({ skill: 'template-renderer', args: { templateName: 'specification-template', outputPath: '.claude/context/artifacts/specifications/my-feature-spec.md', tokens: { FEATURE_NAME: 'User Authentication', VERSION: '1.0.0', AUTHOR: 'Claude', DATE: '2026-01-28', STATUS: 'draft', ACCEPTANCE_CRITERIA_1: 'User can log in with email and password', ACCEPTANCE_CRITERIA_2: 'Password meets complexity requirements', ACCEPTANCE_CRITERIA_3: 'Failed login attempts are logged', }, }, });
Example 2: Render Plan Template
Skill({ skill: 'template-renderer', args: { templateName: 'plan-template', outputPath: '.claude/context/plans/my-feature-plan.md', tokens: { PLAN_TITLE: 'User Authentication Implementation Plan', DATE: '2026-01-28', FRAMEWORK_VERSION: 'Agent-Studio v2.2.1', STATUS: 'Phase 0 - Research', EXECUTIVE_SUMMARY: 'Implementation plan for JWT-based authentication...', TOTAL_TASKS: '14 atomic tasks', ESTIMATED_TIME: '2-3 weeks', STRATEGY: 'Foundation-first (schema) → Core features', }, }, });
Example 3: Render Tasks Template
Skill({ skill: 'template-renderer', args: { templateName: 'tasks-template', outputPath: '.claude/context/artifacts/tasks/auth-tasks.md', tokens: { FEATURE_NAME: 'user-authentication', VERSION: '1.0.0', AUTHOR: 'Engineering Team', DATE: '2026-01-28', FEATURE_DISPLAY_NAME: 'User Authentication', FEATURE_DESCRIPTION: 'JWT-based authentication system', BUSINESS_VALUE: 'Enables user account management', USER_IMPACT: 'Users can securely access personalized features', }, }, });
Example 4: CLI Usage
Using CLI wrapper (after implementation in main.cjs)
node .claude/skills/template-renderer/scripts/main.cjs
--template specification-template
--output ./my-spec.md
--tokens '{"FEATURE_NAME":"My Feature","VERSION":"1.0.0","AUTHOR":"Claude","DATE":"2026-01-28"}'
Or with JSON file
node .claude/skills/template-renderer/scripts/main.cjs
--template plan-template
--output ./my-plan.md
--tokens-file ./tokens.json
Example 5: Integration with spec-gathering
// In spec-gathering skill (Task #16): // After collecting requirements via progressive disclosure...
const tokens = { FEATURE_NAME: gatheredRequirements.featureName, VERSION: '1.0.0', AUTHOR: 'Claude', DATE: new Date().toISOString().split('T')[0], ACCEPTANCE_CRITERIA_1: gatheredRequirements.criteria[0], ACCEPTANCE_CRITERIA_2: gatheredRequirements.criteria[1], ACCEPTANCE_CRITERIA_3: gatheredRequirements.criteria[2], // ... more tokens };
Skill({
skill: 'template-renderer',
args: {
templateName: 'specification-template',
outputPath: .claude/context/artifacts/specifications/${featureName}-spec.md,
tokens: tokens,
},
});
</usage_example>
Memory Protocol (MANDATORY)
Before starting:
cat .claude/context/memory/learnings.md
After completing:
-
New pattern -> .claude/context/memory/learnings.md
-
Issue found -> .claude/context/memory/issues.md
-
Decision made -> .claude/context/memory/decisions.md
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.