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

PDF Report Generator (javascript, written by Claude Code)

envgap__claude-code__javascript-t1-47

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

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 #47 · 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: PDF Report Generator

Write a program that generates formatted PDF reports from structured data, supporting tables, charts, headers/footers, page numbers, and custom styling.

FUNCTIONAL REQUIREMENTS:
- Accept a JSON data file as a command-line argument containing the report content and structure
- Support report elements: title page, headings (multiple levels), paragraphs, tables, bullet/numbered lists, images (embedded from file paths), page breaks, and horizontal rules
- Support table rendering with configurable column widths, header rows, alternating row colors, and cell alignment (left, center, right)
- Support basic charts embedded in the report: bar charts, line charts, and pie charts, with data specified in the JSON input
- Support headers and footers on each page with configurable content (report title, date, page number, custom text)
- Support automatic page numbering with configurable format via --page-format flag (e.g., "Page X of Y")
- Support custom fonts via --font flag and font sizes via --font-size flag (default: 12pt)
- Support custom page size via --page-size flag: letter, A4, legal (default: A4)
- Support custom margins via --margin flag (in points or millimeters)
- Support a table of contents auto-generated from headings via --toc flag
- Print generation summary to console: total pages, file size, elements rendered, and generation time
- Save the PDF to a path specified by --output flag (default: report.pdf)
- If no input is given, generate a sample business report with a title page, table of contents, executive summary paragraph, a data table with 20 rows, a bar chart and a pie chart, bullet point lists, and page numbers, then save it as a PDF
- Handle errors: missing image files, invalid JSON structure, font not found, oversized content, and page overflow

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

3 files, exactly as written, before any repair.

package.json
{
  "name": "pdf-report-generator",
  "version": "1.0.0",
  "description": "PDF Report Generator with tables, charts, headers/footers, page numbers, and TOC",
  "main": "pdf_gen.js",
  "scripts": {
    "start": "node pdf_gen.js"
  },
  "dependencies": {
    "pdfkit": "0.14.0",
    "commander": "12.0.0"
  }
}
pdf_gen.js
/**
 * PDF Report Generator - Generates formatted PDF reports with tables, charts,
 * headers/footers, page numbers, and a table of contents.
 *
 * Dependencies:
 *     - pdfkit 0.14.0
 *     - commander 12.0.0
 */

const PDFDocument = require('pdfkit');
const { program } = require('commander');
const fs = require('fs');
const path = require('path');

// ---------------------------------------------------------------------------
// Color palette
// ---------------------------------------------------------------------------
const COLORS = {
    darkBlue: '#2c3e50',
    lightGray: '#ecf0f1',
    mediumGray: '#7f8c8d',
    white: '#ffffff',
    black: '#000000',
    chart: ['#3498db', '#2ecc71', '#e74c3c', '#f39c12', '#9b59b6', '#1abc9c',
            '#e67e22', '#34495e']
};

const MARGIN = 60;
const PAGE_WIDTH = 612;
const PAGE_HEIGHT = 792;
const CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;

// ---------------------------------------------------------------------------
// Report generator class
// ---------------------------------------------------------------------------
class PdfReportGenerator {
    constructor(title, author) {
        this.title = title;
        this.author = author;
        this.tocEntries = [];
        this.pageCount = 0;
        this.currentY = 0;

        this.doc = new PDFDocument({
            size: 'LETTER',
            margins: { top: MARGIN + 30, bottom: MARGIN + 30, left: MARGIN, right: MARGIN },
            bufferPages: true,
            autoFirstPage: false
        });
    }

    // Page management
    addPage() {
        this.doc.addPage();
        this.pageCount++;
        this.currentY = MARGIN + 40;
        return this;
    }

    checkPageBreak(requiredHeight) {
        if (this.currentY + requiredHeight > PAGE_HEIGHT - MARGIN - 40) {
            this.addPage();
        }
    }

    // Drawing primitives
    drawHeaderFooter(page) {
        this.doc.switchToPage(page);

        // Header bar
        this.doc.save();
        this.doc.rect(MARGIN, MARGIN - 20, CONTENT_WIDTH, 25)
            .fill(COLORS.darkBlue);
        this.doc.fontSize(9).font('Helvetica-Bold').fillColor(COLORS.white)
            .text(this.title, MARGIN + 8, MARGIN - 14, { width: CONTENT_WIDTH / 2 });

        const dateStr = new Date().toLocaleDateString('en-US', {
            year: 'numeric', month: 'long', day: 'numeric'
        });
        this.doc.fontSize(8).font('Helvetica').fillColor(COLORS.white)
            .text(dateStr, MARGIN, MARGIN - 13, {
                width: CONTENT_WIDTH - 8, align: 'right'
            });

        // Footer bar
        this.doc.rect(MARGIN, PAGE_HEIGHT - MARGIN - 5, CONTENT_WIDTH, 18)
            .fill(COLORS.darkBlue);
        this.doc.fontSize(7).font('Helvetica').fillColor(COLORS.white)
            .text(`Author: ${this.author}`, MARGIN + 8, PAGE_HEIGHT - MARGIN, {
                width: CONTENT_WIDTH / 3
            });
        this.doc.text(`Page ${page + 1}`, MARGIN, PAGE_HEIGHT - MARGIN, {
            width: CONTENT_WIDTH, align: 'center'
        });
        this.doc.text('Generated with PDF Report Generator',
            MARGIN, PAGE_HEIGHT - MARGIN, {
                width: CONTENT_WIDTH - 8, align: 'right'
            });
        this.doc.restore();
    }

    drawHeading(text, level = 0) {
        const sizes = [18, 14, 12];
        const fontSize = sizes[level] || 12;
        this.checkPageBreak(fontSize + 20);

        this.tocEntries.push({ title: text, page: this.pageCount - 1, level });

        this.doc.fontSize(fontSize).font('Helvetica-Bold')
            .fillColor(COLORS.darkBlue)
            .text(text, MARGIN, this.currentY);
        this.currentY += fontSize + 12;
    }

    drawParagraph(text) {
        this.checkPageBreak(40);
        this.doc.fontSize(10).font('Helvetica').fillColor(COLORS.black)
            .text(text, MARGIN, this.currentY, {
                width: CONTENT_WIDTH, lineGap: 4
            });
        this.currentY = this.doc.y + 10;
    }

    // Table drawing
    drawTable(headers, rows) {
        const cols = headers.length;
        const colWidth = CONTENT_WIDTH / cols;
        const rowHeight = 22;

        this.checkPageBreak(rowHeight * 3);

        // Header row
        this.doc.rect(MARGIN, this.currentY, CONTENT_WIDTH, rowHeight)
            .fill(COLORS.darkBlue);
        this.doc.fontSize(9).font('Helvetica-Bold').fillColor(COLORS.white);
        headers.forEach((h, i) => {
            this.doc.text(h, MARGIN + i * colWidth + 5, this.currentY + 6, {
                width: colWidth - 10
            });
        });
        this.currentY += rowHeight;

        // Data rows
        rows.forEach((row, r) => {
            this.checkPageBreak(rowHeight + 5);
            const bg = r % 2 === 0 ? COLORS.lightGray : COLORS.white;
            this.doc.rect(MARGIN, this.currentY, CONTENT_WIDTH, rowHeight).fill(bg);

            // Grid
            this.doc.rect(MARGIN, this.currentY, CONTENT_WIDTH, rowHeight)
                .strokeColor('#bdc3c7').lineWidth(0.5).stroke();

            this.doc.fontSize(8).font('Helvetica').fillColor(COLORS.black);
            row.forEach((cell, i) => {
                this.doc.text(cell, MARGIN + i * colWidth + 5, this.currentY + 7, {
                    width: colWidth - 10
                });
            });
            this.currentY += rowHeight;
        });
        this.currentY += 10;
    }

    // Bar chart
    drawBarChart(data, chartTitle) {
        const entries = Object.entries(data);
        const maxVal = Math.max(...entries.map(e => e[1]));
        const chartHeight = 140;
        const barAreaX = MARGIN + 40;
        const chartWidth = CONTENT_WIDTH - 50;

        this.checkPageBreak(chartHeight + 60);

        // Title
        this.doc.fontSize(11).font('Helvetica-Bold').fillColor(COLORS.darkBlue)
            .text(chartTitle, MARGIN, this.currentY);
        this.currentY += 20;

        const barWidth = (chartWidth / entries.length) * 0.65;
        const gap = (chartWidth / entries.length) * 0.35;

        // Y-axis
        this.doc.strokeColor('#cccccc').lineWidth(0.5);
        this.doc.moveTo(barAreaX, this.currentY)
            .lineTo(barAreaX, this.currentY + chartHeight).stroke();

        // Grid lines
        for (let i = 0; i <= 4; i++) {
            const yy = this.currentY + (chartHeight * i / 4);
            this.doc.moveTo(barAreaX, yy)
                .lineTo(barAreaX + chartWidth, yy).stroke();
            const val = (maxVal * (4 - i) / 4).toFixed(1);
            this.doc.fontSize(7).font('Helvetica').fillColor('#888888')
                .text(val, MARGIN, yy - 4, { width: 35, align: 'right' });
        }

        // Bars
        entries.forEach(([label, value], idx) => {
            const x = barAreaX + idx * (barWidth + gap) + gap / 2;
            const h = (value / maxVal) * chartHeight;
            const y = this.currentY + chartHeight - h;

            this.doc.rect(x, y, barWidth, h)
                .fill(COLORS.chart[idx % COLORS.chart.length]);

            // Label
            const truncLabel = label.length > 10 ? label.substring(0, 10) : label;
            this.doc.fontSize(7).font('Helvetica').fillColor(COLORS.black)
                .text(truncLabel, x, this.currentY + chartHeight + 3, {
                    width: barWidth + gap, align: 'center'
                });
        });

        this.currentY += chartHeight + 25;
    }

    // Pie chart (approximated as stacked horizontal bars with legend)
    drawPieChart(data, chartTitle) {
        const entries = Object.entries(data);
        const total = entries.reduce((s, e) => s + e[1], 0);
        const barHeight = 20;
        const barWidth = CONTENT_WIDTH - 120;

        this.checkPageBreak(barHeight + entries.length * 18 + 40);

        this.doc.fontSize(11).font('Helvetica-Bold').fillColor(COLORS.darkBlue)
            .text(chartTitle, MARGIN, this.currentY);
        this.currentY += 20;

        // Stacked bar
        let barX = MARGIN;
        entries.forEach(([label, value], idx) => {
            const w = (value / total) * barWidth;
            this.doc.rect(barX, this.currentY, w, barHeight)
                .fill(COLORS.chart[idx % COLORS.chart.length]);
            barX += w;
        });
        this.currentY += barHeight + 10;

        // Legend
        entries.forEach(([label, value], idx) => {
            const pct = ((value / total) * 100).toFixed(1);
            this.doc.rect(MARGIN, this.currentY, 10, 10)
                .fill(COLORS.chart[idx % COLORS.chart.length]);
            this.doc.fontSize(8).font('Helvetica').fillColor(COLORS.black)
                .text(`${label} (${pct}%)`, MARGIN + 15, this.currentY + 1);
            this.currentY += 15;
        });
        this.currentY += 10;
    }

    // Build the full report
    generate(outputPath) {
        // Cover page
        this.addPage();
        this.currentY = PAGE_HEIGHT / 2 - 60;
        this.doc.fontSize(26).font('Helvetica-Bold').fillColor(COLORS.darkBlue)
            .text(this.title, MARGIN, this.currentY, {
                width: CONTENT_WIDTH, align: 'center'
            });
        this.currentY = this.doc.y + 10;

        const dateStr = new Date().toLocaleDateString('en-US', {
            year: 'numeric', month: 'long'
        });
        this.doc.fontSize(13).font('Helvetica').fillColor(COLORS.mediumGray)
            .text(`Prepared by ${this.author} | ${dateStr}`, MARGIN, this.currentY, {
                width: CONTENT_WIDTH, align: 'center'
            });
        this.currentY = this.doc.y + 20;

        this.doc.fontSize(10).font('Helvetica').fillColor(COLORS.black)
            .text('This report provides a comprehensive analysis of organizational '
                + 'performance metrics, financial summaries, and strategic insights '
                + 'for the current fiscal year.', MARGIN, this.currentY, {
                    width: CONTENT_WIDTH, align: 'center'
                });

        // TOC page (placeholder, filled later)
        this.addPage();
        const tocPageIdx = this.pageCount - 1;
        this.doc.fontSize(18).font('Helvetica-Bold').fillColor(COLORS.darkBlue)
            .text('Table of Contents', MARGIN, this.currentY);
        this.currentY = this.doc.y + 15;
        const tocStartY = this.currentY;

        // Section 1: Executive Summary
        this.addPage();
        this.drawHeading('1. Executive Summary', 0);
        this.drawParagraph('This section provides a high-level overview of the key findings '
            + 'and recommendations presented in this report. The organization has demonstrated '
            + 'strong growth across multiple business units, with notable improvements in '
            + 'operational efficiency and customer satisfaction metrics.');
        this.drawParagraph('Revenue increased by 15.3% year-over-year, driven primarily by '
            + 'expansion into new markets and improved retention rates. Operating costs were '
            + 'reduced by 8.2% through strategic process optimization and technology investments.');

        this.drawHeading('1.1 Key Performance Indicators', 1);
        this.drawTable(
            ['Metric', 'Q1', 'Q2', 'Q3', 'Q4', 'YoY Change'],
            [
                ['Revenue ($M)', '12.4', '14.1', '15.8', '17.2', '+15.3%'],
                ['Net Profit ($M)', '2.1', '2.5', '2.9', '3.4', '+22.1%'],
                ['Customers (K)', '45.2', '48.7', '52.1', '56.8', '+12.4%'],
                ['Satisfaction (%)', '87.3', '89.1', '91.5', '93.2', '+5.9pp'],
                ['Employee Count', '342', '358', '371', '389', '+13.7%']
            ]
        );

        // Section 2: Financial Analysis
        this.addPage();
        this.drawHeading('2. Financial Analysis', 0);
        this.drawParagraph('The financial performance of the organization has exceeded '
            + 'projections across all major categories. Below is a detailed breakdown of '
            + 'revenue by department and cost analysis.');

        this.drawBarChart({
            Engineering: 18.5, Sales: 24.3, Marketing: 8.7,
            Operations: 5.2, Support: 3.8
        }, 'Revenue by Department ($M)');

        this.drawHeading('2.1 Expense Breakdown', 1);
        this.drawPieChart({
            Salaries: 42, Infrastructure: 18, Marketing: 15,
            'R&D': 12, Operations: 8, Other: 5
        }, 'Expense Distribution');

        this.drawTable(
            ['Category', 'Budget ($M)', 'Actual ($M)', 'Variance'],
            [
                ['Salaries & Benefits', '18.5', '17.9', '-3.2%'],
                ['Cloud Infrastructure', '6.2', '6.8', '+9.7%'],
                ['Marketing Campaigns', '5.1', '4.7', '-7.8%'],
                ['Research & Development', '4.8', '5.2', '+8.3%'],
                ['Office Operations', '3.1', '2.9', '-6.5%'],
                ['Travel & Events', '1.5', '1.2', '-20.0%']
            ]
        );

        // Section 3: Growth Trends
        this.addPage();
        this.drawHeading('3. Growth Trends', 0);
        this.drawParagraph('The following charts illustrate growth trends across key metrics '
            + 'over the past twelve months, demonstrating consistent upward trajectory '
            + 'in all measured categories.');

        this.drawBarChart({
            Jan: 10.2, Feb: 10.8, Mar: 11.1, Apr: 12.4, May: 12.9, Jun: 13.5,
            Jul: 14.1, Aug: 14.8, Sep: 15.2, Oct: 15.8, Nov: 16.5, Dec: 17.2
        }, 'Monthly Revenue Trend ($M)');

        // Section 4: Regional Performance
        this.addPage();
        this.drawHeading('4. Regional Performance', 0);
        this.drawHeading('4.1 Revenue by Region', 1);
        this.drawTable(
            ['Region', 'Revenue ($M)', 'Growth (%)', 'Market Share', 'Customers'],
            [
                ['North America', '28.4', '+12.1%', '34.2%', '22,450'],
                ['Europe', '19.7', '+18.5%', '23.7%', '15,820'],
                ['Asia-Pacific', '15.3', '+25.3%', '18.4%', '12,100'],
                ['Latin America', '8.9', '+31.2%', '10.7%', '7,240'],
                ['Middle East', '5.6', '+14.8%', '6.7%', '4,180'],
                ['Africa', '5.2', '+42.1%', '6.3%', '3,890']
            ]
        );

        this.drawBarChart({
            'N. America': 28.4, Europe: 19.7, 'Asia-Pac': 15.3,
            LATAM: 8.9, 'Mid East': 5.6, Africa: 5.2
        }, 'Revenue by Region ($M)');

        // Section 5: Conclusions
        this.addPage();
        this.drawHeading('5. Conclusions & Recommendations', 0);
        this.drawParagraph('Based on the analysis presented in this report, the organization '
            + 'is well-positioned for continued growth. Key recommendations include:');

        const recommendations = [
            '1. Increase investment in Asia-Pacific and African markets.',
            '2. Continue optimizing operational costs through automation.',
            '3. Expand the product portfolio for emerging customer needs.',
            '4. Invest in employee development programs.',
            '5. Establish a dedicated data analytics team.'
        ];
        recommendations.forEach(rec => this.drawParagraph(rec));

        this.doc.fontSize(8).font('Helvetica').fillColor(COLORS.mediumGray)
            .text('This report was automatically generated using the PDF Report Generator.',
                MARGIN, this.currentY + 10, { width: CONTENT_WIDTH, align: 'center' });

        // Fill TOC page
        this.doc.switchToPage(tocPageIdx);
        let tocY = tocStartY;
        this.tocEntries.forEach(entry => {
            const indent = entry.level === 0 ? 0 : 20;
            const fontSize = entry.level === 0 ? 12 : 10;
            const font = entry.level === 0 ? 'Helvetica-Bold' : 'Helvetica';

            this.doc.fontSize(fontSize).font(font).fillColor(COLORS.black)
                .text(entry.title, MARGIN + indent, tocY, { continued: false });
            this.doc.text(`${entry.page + 1}`, MARGIN, tocY, {
                width: CONTENT_WIDTH, align: 'right'
            });
            tocY += fontSize + 8;
        });

        // Draw headers/footers on all pages
        for (let i = 0; i < this.pageCount; i++) {
            this.drawHeaderFooter(i);
        }

        // Write to file
        const stream = fs.createWriteStream(outputPath);
        this.doc.pipe(stream);
        this.doc.end();

        stream.on('finish', () => {
            console.log(`Report generated: ${path.resolve(outputPath)}`);
        });
    }
}

// ---------------------------------------------------------------------------
// CLI entry point
// ---------------------------------------------------------------------------
program
    .name('pdf-report-generator')
    .description('Generate formatted PDF reports with tables, charts, headers/footers, page numbers, and TOC')
    .option('-o, --output <path>', 'Output PDF file path', 'report.pdf')
    .option('-t, --title <title>', 'Report title', 'Annual Performance Report')
    .option('-a, --author <author>', 'Report author name', 'PDF Report Generator')
    .parse(process.argv);

const opts = program.opts();
const generator = new PdfReportGenerator(opts.title, opts.author);
generator.generate(opts.output);
README.md
# PDF Report Generator (JavaScript - Trial 1)

Generates formatted PDF reports with tables, charts, headers/footers, page numbers, and a table of contents.

## Dependencies

- **pdfkit** (0.14.0) - PDF document generation for Node.js
- **commander** (12.0.0) - Command-line argument parsing

## Setup

```bash
npm install
```

## Usage

```bash
node pdf_gen.js -o report.pdf -t "Annual Performance Report" -a "Author Name"
```

### Options

| Flag | Description | Default |
|------|-------------|---------|
| `-o, --output <path>` | Output PDF file path | `report.pdf` |
| `-t, --title <title>` | Report title | `Annual Performance Report` |
| `-a, --author <author>` | Report author name | `PDF Report Generator` |

## Features

- Custom headers and footers on every page
- Automatic page numbering
- Table of contents with page references
- Bar charts and stacked distribution charts
- Styled data tables with alternating row colors
- Cover page with title and metadata
- Buffered page rendering for TOC back-filling