← All tasks
javascriptclaude-code/javascript-t1 #35Not a task: already works

Template Engine (javascript, written by Claude Code)

envgap__claude-code__javascript-t1-35

Written by a coding agent; not on GitHubWritten 2026-02-27

01 / FAILURE SIGNATURE

As the study recorded it

No identifying execution failure has been captured.
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
package.json
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/javascript-t1 #35 · read the task the agent was given
Claude Code wrote this javascript project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Template Engine

Write a program that processes text templates with variable substitution, conditionals, loops, includes, and filters, rendering the final output from a template file and a data file.

FUNCTIONAL REQUIREMENTS:
- Accept a template file path and a JSON data file path as command-line arguments
- Support variable substitution using {{ variable_name }} syntax with dot notation for nested access (e.g., {{ user.name }})
- Support conditional blocks: {% if condition %} ... {% elif condition %} ... {% else %} ... {% endif %} with support for comparison operators (==, !=, <, >, <=, >=) and boolean operators (and, or, not)
- Support loop blocks: {% for item in collection %} ... {% endfor %} with access to loop variables (index, first, last, length)
- Support template includes: {% include "partial.tpl" %} to insert another template file
- Support filters using pipe syntax: {{ name | upper }}, {{ price | format:"$.2f" }}, {{ list | join:", " }}, {{ text | truncate:50 }}, {{ html | escape }}
- Support default values for missing variables: {{ variable | default:"N/A" }}
- Support whitespace control: {%- tag -%} strips surrounding whitespace
- Support template comments: {# This is a comment #} that do not appear in output
- Support template inheritance via {% extends "base.tpl" %} and {% block name %} ... {% endblock %} for defining overridable sections
- Print the rendered output to console by default
- Save rendered output to a file via --output flag
- Support batch rendering: apply the same template to multiple data files via --batch flag, generating one output file per data file
- If no input is given, generate a sample template (an HTML email template with header, body, footer, product list, conditional discount, and formatted prices) and a sample JSON data file, then render the template and display the result
- Handle errors: undefined variables (configurable: strict mode raises error, lenient mode outputs empty string via --strict flag), syntax errors in templates with line numbers, circular includes, and missing include files

Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include:
- Source code
- package.json with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

2 files, exactly as written, before any repair.

engine.js
/**
 * Template Engine - Processes templates with variables, conditionals, loops,
 * includes, filters, and inheritance using Handlebars.
 *
 * Dependencies: handlebars 4.7.8
 */

const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');

class TemplateEngine {
    /**
     * Initialize the template engine.
     * @param {string[]} templateDirs - Directories to search for templates.
     * @param {boolean} autoEscape - Whether to enable HTML auto-escaping.
     */
    constructor(templateDirs = [process.cwd()], autoEscape = true) {
        this.templateDirs = templateDirs;
        this.autoEscape = autoEscape;
        this.globalContext = {};
        this.customFilters = {};
        this.partials = {};
        this.layouts = {};

        this._registerDefaultHelpers();
        this._loadPartialsFromDirs();
    }

    /**
     * Register built-in custom helpers (filters).
     */
    _registerDefaultHelpers() {
        // Slugify helper
        Handlebars.registerHelper('slugify', function (value) {
            if (!value) return '';
            return String(value)
                .toLowerCase()
                .trim()
                .replace(/[^\w\s-]/g, '')
                .replace(/[\s_]+/g, '-')
                .replace(/-+/g, '-')
                .replace(/^-|-$/g, '');
        });

        // Truncate words helper
        Handlebars.registerHelper('truncateWords', function (value, count, options) {
            if (!value) return '';
            const words = String(value).split(/\s+/);
            const limit = typeof count === 'number' ? count : 10;
            if (words.length <= limit) return value;
            return words.slice(0, limit).join(' ') + '...';
        });

        // Format currency helper
        Handlebars.registerHelper('formatCurrency', function (value, symbol) {
            const sym = typeof symbol === 'string' ? symbol : '$';
            const num = parseFloat(value);
            if (isNaN(num)) return String(value);
            return sym + num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
        });

        // Repeat helper
        Handlebars.registerHelper('repeat', function (value, times) {
            const count = typeof times === 'number' ? times : 2;
            return String(value).repeat(count);
        });

        // Upper case helper
        Handlebars.registerHelper('upper', function (value) {
            return String(value).toUpperCase();
        });

        // Lower case helper
        Handlebars.registerHelper('lower', function (value) {
            return String(value).toLowerCase();
        });

        // Title case helper
        Handlebars.registerHelper('titleCase', function (value) {
            return String(value).replace(/\w\S*/g, txt =>
                txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
            );
        });

        // Sort by helper for arrays
        Handlebars.registerHelper('sortBy', function (array, attribute, options) {
            if (!Array.isArray(array)) return array;
            return array.slice().sort((a, b) => {
                const va = a[attribute] || '';
                const vb = b[attribute] || '';
                return String(va).localeCompare(String(vb));
            });
        });

        // Equality check helper
        Handlebars.registerHelper('eq', function (a, b) {
            return a === b;
        });

        // Length helper
        Handlebars.registerHelper('length', function (value) {
            if (Array.isArray(value)) return value.length;
            return String(value).length;
        });

        // Block extend/layout helpers for inheritance
        Handlebars.registerHelper('extend', (layoutName, options) => {
            const layoutContent = this._loadTemplate(layoutName);
            if (!layoutContent) {
                throw new Error(`Layout '${layoutName}' not found`);
            }
            return layoutContent;
        });

        // Join helper
        Handlebars.registerHelper('join', function (array, separator) {
            if (!Array.isArray(array)) return '';
            const sep = typeof separator === 'string' ? separator : ', ';
            return array.join(sep);
        });
    }

    /**
     * Load partial templates from template directories.
     */
    _loadPartialsFromDirs() {
        for (const dir of this.templateDirs) {
            if (!fs.existsSync(dir)) continue;
            const files = fs.readdirSync(dir);
            for (const file of files) {
                const filePath = path.join(dir, file);
                if (fs.statSync(filePath).isFile() && /\.(hbs|handlebars|html)$/.test(file)) {
                    const name = path.basename(file, path.extname(file));
                    const content = fs.readFileSync(filePath, 'utf-8');
                    Handlebars.registerPartial(name, content);
                    this.partials[name] = content;
                }
            }
        }
    }

    /**
     * Load a template file from the search directories.
     * @param {string} templateName - Name of the template file.
     * @returns {string|null} Template content or null.
     */
    _loadTemplate(templateName) {
        for (const dir of this.templateDirs) {
            const filePath = path.join(dir, templateName);
            if (fs.existsSync(filePath)) {
                return fs.readFileSync(filePath, 'utf-8');
            }
        }
        return null;
    }

    /**
     * Register a custom filter (helper).
     * @param {string} name - Helper name usable in templates.
     * @param {Function} func - The helper function.
     */
    addFilter(name, func) {
        this.customFilters[name] = func;
        Handlebars.registerHelper(name, func);
    }

    /**
     * Set a global variable available in all templates.
     * @param {string} key - Variable name.
     * @param {*} value - Variable value.
     */
    setGlobal(key, value) {
        this.globalContext[key] = value;
    }

    /**
     * Render a template from a string.
     * @param {string} templateString - Template content.
     * @param {Object} context - Variables to pass to the template.
     * @returns {string} Rendered output.
     */
    renderString(templateString, context = {}) {
        const mergedContext = { ...this.globalContext, ...context };
        const template = Handlebars.compile(templateString, { noEscape: !this.autoEscape });
        return template(mergedContext);
    }

    /**
     * Render a template from a file.
     * @param {string} templateName - Template file name.
     * @param {Object} context - Variables to pass to the template.
     * @returns {string} Rendered output.
     */
    renderFile(templateName, context = {}) {
        const content = this._loadTemplate(templateName);
        if (!content) {
            throw new Error(`Template '${templateName}' not found in: ${this.templateDirs.join(', ')}`);
        }
        return this.renderString(content, context);
    }

    /**
     * Render a template and write the output to a file.
     * @param {string} templateName - Template file name.
     * @param {string} outputPath - Path to write rendered output.
     * @param {Object} context - Variables to pass to the template.
     */
    renderToFile(templateName, outputPath, context = {}) {
        const result = this.renderFile(templateName, context);
        const dir = path.dirname(outputPath);
        if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true });
        }
        fs.writeFileSync(outputPath, result, 'utf-8');
    }

    /**
     * Render a child template that extends a base layout.
     * Uses Handlebars partials for inheritance simulation.
     * @param {string} childTemplate - Child template file name.
     * @param {Object} context - Variables to pass to the template.
     * @returns {string} Rendered output.
     */
    renderWithInheritance(childTemplate, context = {}) {
        return this.renderFile(childTemplate, context);
    }
}

/**
 * Demonstrate rendering templates from strings.
 */
function demoStringTemplates() {
    const engine = new TemplateEngine();

    // Variables
    console.log('=== Variables ===');
    let result = engine.renderString(
        'Hello, {{name}}! Welcome to {{place}}.',
        { name: 'Alice', place: 'Wonderland' }
    );
    console.log(result);

    // Conditionals
    console.log('\n=== Conditionals ===');
    const conditionalTpl = `{{#if user.isAdmin}}
Welcome, Administrator {{user.name}}!
{{else if user.isMember}}
Welcome back, {{user.name}}!
{{else}}
Welcome, guest! Please sign up.
{{/if}}`;
    result = engine.renderString(conditionalTpl, {
        user: { name: 'Bob', isAdmin: false, isMember: true }
    });
    console.log(result.trim());

    // Loops
    console.log('\n=== Loops ===');
    const loopTpl = `Shopping List:
{{#each items}}
{{@index}}. {{this.name}} - {{formatCurrency this.price}}
{{/each}}
Total items: {{length items}}`;
    result = engine.renderString(loopTpl, {
        items: [
            { name: 'Apples', price: 3.50 },
            { name: 'Bread', price: 2.99 },
            { name: 'Milk', price: 4.25 }
        ]
    });
    console.log(result.trim());

    // Filters
    console.log('\n=== Filters ===');
    const filterTpl = `Upper: {{upper name}}
Lower: {{lower name}}
Title: {{titleCase sentence}}
Slug: {{slugify title}}
Truncated: {{truncateWords longText 5}}
Currency: {{formatCurrency price "EUR "}}`;
    result = engine.renderString(filterTpl, {
        name: 'alice wonderland',
        sentence: 'hello world from template engine',
        title: 'My Blog Post Title!',
        longText: 'This is a very long text that should be truncated after five words',
        price: 1234.56
    });
    console.log(result.trim());

    // Custom filter
    console.log('\n=== Custom Filter ===');
    engine.addFilter('reverseWords', function (text) {
        return String(text).split(/\s+/).reverse().join(' ');
    });
    result = engine.renderString(
        'Reversed: {{reverseWords text}}',
        { text: 'Hello Beautiful World' }
    );
    console.log(result);

    // Global variables
    console.log('\n=== Global Variables ===');
    engine.setGlobal('appName', 'TemplateEngine Pro');
    engine.setGlobal('version', '1.0.0');
    result = engine.renderString('Running {{appName}} v{{version}}');
    console.log(result);
}

/**
 * Demonstrate file-based templates with includes and inheritance.
 */
function demoFileTemplates(templateDir) {
    if (!fs.existsSync(templateDir)) {
        fs.mkdirSync(templateDir, { recursive: true });
    }

    // Base layout template
    const baseTpl = `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{#if title}}{{title}}{{else}}Default Title{{/if}}</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        header { background: #333; color: white; padding: 10px; }
        footer { background: #333; color: white; padding: 10px; margin-top: 20px; }
        .content { padding: 20px; }
    </style>
</head>
<body>
    <header>
        <h1>{{#if siteName}}{{siteName}}{{else}}My Website{{/if}}</h1>
    </header>
    <div class="content">
        {{> @partial-block }}
    </div>
    <footer>
        <p>&copy; 2024 {{#if siteName}}{{siteName}}{{else}}My Website{{/if}}</p>
    </footer>
</body>
</html>`;

    // Blog child template using partial for inheritance
    const childTpl = `{{#> base title=pageTitle}}
<h2>{{pageTitle}}</h2>
{{#each articles}}
<article>
    <h3>{{this.title}}</h3>
    <p class="meta">By {{this.author}} on {{this.date}}</p>
    <p>{{truncateWords this.summary 15}}</p>
    {{#if this.tags}}
    <div class="tags">
        Tags: {{#each this.tags}}{{slugify this}}{{#unless @last}}, {{/unless}}{{/each}}
    </div>
    {{/if}}
</article>
{{/each}}
{{/base}}`;

    // Navigation partial
    const navTpl = `<nav>
    <ul>
    {{#each navLinks}}
        <li><a href="{{this.url}}">{{this.label}}</a></li>
    {{/each}}
    </ul>
</nav>`;

    // Page with include
    const pageWithNavTpl = `<div>
{{> nav}}
<main>
    <h1>Welcome, {{userName}}!</h1>
</main>
</div>`;

    // Write template files
    fs.writeFileSync(path.join(templateDir, 'base.hbs'), baseTpl);
    fs.writeFileSync(path.join(templateDir, 'blog.hbs'), childTpl);
    fs.writeFileSync(path.join(templateDir, 'nav.hbs'), navTpl);
    fs.writeFileSync(path.join(templateDir, 'page_with_nav.hbs'), pageWithNavTpl);

    const engine = new TemplateEngine([templateDir]);

    // Render blog (inheritance via partial blocks)
    console.log('=== Template Inheritance ===');
    const blogContext = {
        siteName: 'Tech Blog',
        pageTitle: 'Latest Articles',
        articles: [
            {
                title: 'Getting Started with Handlebars',
                author: 'Alice',
                date: '2024-01-15',
                summary: 'Handlebars is a powerful template engine for JavaScript that provides logic-less templates with helpers and partials for building dynamic web content.',
                tags: ['JavaScript', 'Web Dev', 'Templates']
            },
            {
                title: 'Advanced Template Patterns',
                author: 'Bob',
                date: '2024-02-20',
                summary: 'Learn how to use partial blocks, custom helpers, and advanced patterns to build maintainable and reusable template structures for your projects.',
                tags: ['Advanced', 'Best Practices']
            }
        ]
    };
    console.log(engine.renderFile('blog.hbs', blogContext));

    // Render page with includes
    console.log('\n=== Template Includes ===');
    const navContext = {
        userName: 'Charlie',
        navLinks: [
            { url: '/', label: 'Home' },
            { url: '/about', label: 'About' },
            { url: '/blog', label: 'Blog' },
            { url: '/contact', label: 'Contact' }
        ]
    };
    console.log(engine.renderFile('page_with_nav.hbs', navContext));
}

/**
 * Main entry point with CLI support.
 */
function main() {
    const args = process.argv.slice(2);

    if (args.includes('--demo')) {
        console.log('='.repeat(60));
        console.log('Template Engine Demo - Handlebars');
        console.log('='.repeat(60));
        console.log();
        demoStringTemplates();
        console.log();
        const templateDir = path.join(process.cwd(), 'templates', 'demo');
        demoFileTemplates(templateDir);
        return;
    }

    const templateIdx = args.indexOf('--template');
    const tIdx = args.indexOf('-t');
    const templateArg = templateIdx >= 0 ? args[templateIdx + 1] : (tIdx >= 0 ? args[tIdx + 1] : null);

    const dataIdx = args.indexOf('--data');
    const dIdx = args.indexOf('-d');
    const dataArg = dataIdx >= 0 ? args[dataIdx + 1] : (dIdx >= 0 ? args[dIdx + 1] : null);

    const outputIdx = args.indexOf('--output');
    const oIdx = args.indexOf('-o');
    const outputArg = outputIdx >= 0 ? args[outputIdx + 1] : (oIdx >= 0 ? args[oIdx + 1] : null);

    const dirIdx = args.indexOf('--template-dir');
    const templateDirArg = dirIdx >= 0 ? args[dirIdx + 1] : './templates';

    if (templateArg) {
        const templateDir = path.dirname(path.resolve(templateArg));
        const engine = new TemplateEngine([templateDir]);

        let context = {};
        if (dataArg) {
            context = JSON.parse(fs.readFileSync(dataArg, 'utf-8'));
        }

        const templateName = path.basename(templateArg);
        if (outputArg) {
            engine.renderToFile(templateName, outputArg, context);
            console.log(`Rendered output written to: ${outputArg}`);
        } else {
            const result = engine.renderFile(templateName, context);
            console.log(result);
        }
    } else {
        console.log('Template Engine - Handlebars');
        console.log('Usage:');
        console.log('  node engine.js --demo                         Run demo examples');
        console.log('  node engine.js -t <template> [-d data.json] [-o output]  Render template');
        console.log('  node engine.js --template-dir <dir>           Set template directory');
    }
}

module.exports = { TemplateEngine };

if (require.main === module) {
    main();
}
package.json
{
  "name": "template-engine",
  "version": "1.0.0",
  "description": "Template engine with variables, conditionals, loops, includes, filters, and inheritance using Handlebars",
  "main": "engine.js",
  "dependencies": {
    "handlebars": "4.7.8"
  }
}