← All tasks
cppclaude-code/cpp-t1 #33Not a task: already works

Text Diff Tool (cpp, written by Claude Code)

envgap__claude-code__cpp-t1-33

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
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

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

Task given to the agent:

TASK: Text Diff Tool

Write a program that compares two text files and produces a detailed diff showing additions, deletions, and modifications with configurable output formats and context control.

FUNCTIONAL REQUIREMENTS:
- Accept two file paths as command-line arguments (original and modified)
- Compute the longest common subsequence (LCS) based diff to identify added, deleted, and changed lines
- Support multiple output formats via --format flag: unified diff (default, similar to git diff), side-by-side (two-column view), inline (changes marked within lines), and html (visual diff as an HTML page)
- Support configurable context lines around changes via --context flag (default: 3 lines of unchanged context around each change)
- Detect and highlight intra-line changes: when a line is modified, show exactly which words or characters changed within the line
- Support ignoring whitespace differences via --ignore-whitespace flag
- Support ignoring case differences via --ignore-case flag
- Support ignoring blank lines via --ignore-blank-lines flag
- Compute and display diff statistics: total lines in each file, lines added, lines deleted, lines modified, and a similarity percentage
- Support comparing directories via --recursive flag: compare all matching files in two directory trees and report which files are added, deleted, modified, or identical
- Apply color coding in console output: green for additions, red for deletions, yellow for modifications
- Save the diff output to a file via --output flag
- If no input files are given, generate two sample text files (original and modified version with insertions, deletions, modifications, and moved blocks), then compute and display the diff in all supported formats
- Handle errors: binary files (detect and skip with warning), missing files, encoding mismatches, and very large files

Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.txt 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.

CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(text_diff_tool VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# ---------------------------------------------------------------------------
# Dependencies
# ---------------------------------------------------------------------------
# dtl v1.20 — header-only diff template library for C++
# nlohmann/json v3.11.3 — header-only JSON library
# ---------------------------------------------------------------------------

include(FetchContent)

FetchContent_Declare(
    dtl
    GIT_REPOSITORY https://github.com/cubicdaiya/dtl.git
    GIT_TAG        v1.20
    GIT_SHALLOW    TRUE
)
FetchContent_MakeAvailable(dtl)

FetchContent_Declare(
    nlohmann_json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG        v3.11.3
    GIT_SHALLOW    TRUE
)
set(JSON_BuildTests OFF CACHE BOOL "" FORCE)
set(JSON_Install OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(nlohmann_json)

# ---------------------------------------------------------------------------
# Executable
# ---------------------------------------------------------------------------

add_executable(text_diff_tool diff_tool.cpp)

target_include_directories(text_diff_tool PRIVATE ${dtl_SOURCE_DIR})
target_link_libraries(text_diff_tool PRIVATE nlohmann_json::nlohmann_json)

if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(text_diff_tool PRIVATE -Wall -Wextra -Wpedantic)
endif()
diff_tool.cpp
/**
 * Text Diff Tool - Compares text files showing additions, deletions, and modifications.
 * Supports unified, side-by-side, and HTML output formats.
 * Uses dtl (diff template library) for diff computation and nlohmann/json for JSON output.
 */

#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include <dtl/dtl.hpp>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

// ANSI color codes
namespace Color {
    const std::string RESET  = "\033[0m";
    const std::string RED    = "\033[31m";
    const std::string GREEN  = "\033[32m";
    const std::string YELLOW = "\033[33m";
    const std::string CYAN   = "\033[36m";
    const std::string BOLD   = "\033[1m";
}

struct DiffStats {
    int additions = 0;
    int deletions = 0;
    int modifications = 0;
    int file1_lines = 0;
    int file2_lines = 0;
    int total_changes() const { return additions + deletions + modifications; }
};

class TextDiffTool {
public:
    TextDiffTool(const std::string& file1_path, const std::string& file2_path, int context_lines = 3)
        : file1_path_(file1_path), file2_path_(file2_path), context_lines_(context_lines)
    {
        file1_lines_ = readFile(file1_path);
        file2_lines_ = readFile(file2_path);
    }

    /**
     * Display unified diff with ANSI coloring.
     */
    void displayUnified(std::ostream& out) const {
        dtl::Diff<std::string> diff(file1_lines_, file2_lines_);
        diff.compose();
        diff.composeUnifiedHunks();

        auto hunks = diff.getUniHunks();
        if (hunks.empty()) {
            out << Color::GREEN << "Files are identical." << Color::RESET << "\n";
            return;
        }

        out << Color::BOLD << "=== Unified Diff ===" << Color::RESET << "\n";
        out << Color::BOLD << "--- " << file1_path_ << Color::RESET << "\n";
        out << Color::BOLD << "+++ " << file2_path_ << Color::RESET << "\n";

        for (const auto& hunk : hunks) {
            out << Color::CYAN << "@@ -" << hunk.a << "," << hunk.b
                << " +" << hunk.c << "," << hunk.d << " @@" << Color::RESET << "\n";

            for (const auto& sesElem : hunk.change) {
                switch (sesElem.second.type) {
                    case dtl::SES_DELETE:
                        out << Color::RED << "-" << sesElem.second.beforeIdx
                            << ": " << sesElem.first << Color::RESET << "\n";
                        break;
                    case dtl::SES_ADD:
                        out << Color::GREEN << "+" << sesElem.second.afterIdx
                            << ": " << sesElem.first << Color::RESET << "\n";
                        break;
                    case dtl::SES_COMMON:
                        out << " " << sesElem.first << "\n";
                        break;
                }
            }
        }
    }

    /**
     * Display side-by-side diff.
     */
    void displaySideBySide(std::ostream& out, int width = 120) const {
        int colWidth = (width - 7) / 2;
        std::string separator = " | ";

        out << Color::BOLD << "=== Side-by-Side Diff ===" << Color::RESET << "\n";
        out << padRight(basename(file1_path_), colWidth) << separator
            << padRight(basename(file2_path_), colWidth) << "\n";
        out << std::string(colWidth, '-') << "-+-" << std::string(colWidth, '-') << "\n";

        dtl::Diff<std::string> diff(file1_lines_, file2_lines_);
        diff.compose();

        auto ses = diff.getSes().getSequence();
        size_t leftIdx = 0, rightIdx = 0;

        for (const auto& elem : ses) {
            switch (elem.second.type) {
                case dtl::SES_COMMON: {
                    std::string left = truncate(file1_lines_[leftIdx], colWidth);
                    std::string right = truncate(file2_lines_[rightIdx], colWidth);
                    out << padRight(left, colWidth) << separator
                        << padRight(right, colWidth) << "\n";
                    leftIdx++;
                    rightIdx++;
                    break;
                }
                case dtl::SES_DELETE: {
                    std::string left = truncate(file1_lines_[leftIdx], colWidth);
                    out << Color::RED << padRight(left, colWidth) << Color::RESET
                        << separator << padRight("", colWidth) << "\n";
                    leftIdx++;
                    break;
                }
                case dtl::SES_ADD: {
                    std::string right = truncate(file2_lines_[rightIdx], colWidth);
                    out << padRight("", colWidth) << separator
                        << Color::GREEN << padRight(right, colWidth) << Color::RESET << "\n";
                    rightIdx++;
                    break;
                }
            }
        }
    }

    /**
     * Generate HTML diff report.
     */
    std::string generateHtml() const {
        DiffStats stats = computeStats();
        std::ostringstream html;

        html << "<!DOCTYPE html>\n<html>\n<head>\n";
        html << "<meta charset=\"UTF-8\">\n";
        html << "<title>Diff: " << escapeHtml(file1_path_) << " vs "
             << escapeHtml(file2_path_) << "</title>\n";
        html << "<style>\n";
        html << "body { font-family: 'Courier New', monospace; margin: 20px; }\n";
        html << "table { border-collapse: collapse; width: 100%; }\n";
        html << "td, th { border: 1px solid #ddd; padding: 4px 8px; text-align: left; }\n";
        html << "th { background: #f0f0f0; }\n";
        html << ".add { background: #dfd; }\n";
        html << ".del { background: #fdd; }\n";
        html << ".linenum { color: #888; width: 50px; text-align: right; }\n";
        html << ".stats { background: #f8f8f8; padding: 15px; border: 1px solid #ccc; margin-bottom: 20px; }\n";
        html << "</style>\n</head>\n<body>\n";

        html << "<div class=\"stats\">\n";
        html << "<h2>Diff Statistics</h2>\n";
        html << "<p>Additions: <strong style=\"color:green;\">+" << stats.additions << "</strong></p>\n";
        html << "<p>Deletions: <strong style=\"color:red;\">-" << stats.deletions << "</strong></p>\n";
        html << "<p>Modifications: <strong style=\"color:orange;\">~" << stats.modifications << "</strong></p>\n";
        html << "<p>Total changes: " << stats.total_changes() << "</p>\n";
        html << "</div>\n";

        html << "<h2>Side-by-Side Comparison</h2>\n";
        html << "<table>\n<tr><th class=\"linenum\">#</th><th>"
             << escapeHtml(file1_path_) << "</th><th class=\"linenum\">#</th><th>"
             << escapeHtml(file2_path_) << "</th></tr>\n";

        dtl::Diff<std::string> diff(file1_lines_, file2_lines_);
        diff.compose();

        auto ses = diff.getSes().getSequence();
        size_t leftIdx = 0, rightIdx = 0;

        for (const auto& elem : ses) {
            switch (elem.second.type) {
                case dtl::SES_COMMON:
                    html << "<tr><td class=\"linenum\">" << (leftIdx + 1) << "</td>"
                         << "<td>" << escapeHtml(file1_lines_[leftIdx]) << "</td>"
                         << "<td class=\"linenum\">" << (rightIdx + 1) << "</td>"
                         << "<td>" << escapeHtml(file2_lines_[rightIdx]) << "</td></tr>\n";
                    leftIdx++;
                    rightIdx++;
                    break;
                case dtl::SES_DELETE:
                    html << "<tr><td class=\"linenum\">" << (leftIdx + 1) << "</td>"
                         << "<td class=\"del\">" << escapeHtml(file1_lines_[leftIdx]) << "</td>"
                         << "<td class=\"linenum\"></td><td></td></tr>\n";
                    leftIdx++;
                    break;
                case dtl::SES_ADD:
                    html << "<tr><td class=\"linenum\"></td><td></td>"
                         << "<td class=\"linenum\">" << (rightIdx + 1) << "</td>"
                         << "<td class=\"add\">" << escapeHtml(file2_lines_[rightIdx]) << "</td></tr>\n";
                    rightIdx++;
                    break;
            }
        }

        html << "</table>\n</body>\n</html>";
        return html.str();
    }

    /**
     * Save HTML to file.
     */
    void saveHtml(const std::string& outputPath) const {
        std::ofstream ofs(outputPath);
        if (!ofs) {
            std::cerr << "Error: Cannot write to " << outputPath << "\n";
            return;
        }
        ofs << generateHtml();
        ofs.close();
        std::cout << "HTML diff saved to: " << outputPath << "\n";
    }

    /**
     * Compute diff statistics.
     */
    DiffStats computeStats() const {
        dtl::Diff<std::string> diff(file1_lines_, file2_lines_);
        diff.compose();

        DiffStats stats;
        stats.file1_lines = static_cast<int>(file1_lines_.size());
        stats.file2_lines = static_cast<int>(file2_lines_.size());

        auto ses = diff.getSes().getSequence();

        // Pair consecutive deletes and adds as modifications
        size_t i = 0;
        while (i < ses.size()) {
            if (ses[i].second.type == dtl::SES_DELETE) {
                int delCount = 0;
                while (i < ses.size() && ses[i].second.type == dtl::SES_DELETE) {
                    delCount++;
                    i++;
                }
                int addCount = 0;
                while (i < ses.size() && ses[i].second.type == dtl::SES_ADD) {
                    addCount++;
                    i++;
                }
                if (addCount > 0) {
                    stats.modifications += std::max(delCount, addCount);
                } else {
                    stats.deletions += delCount;
                }
            } else if (ses[i].second.type == dtl::SES_ADD) {
                stats.additions++;
                i++;
            } else {
                i++;
            }
        }

        return stats;
    }

    /**
     * Display statistics.
     */
    void displayStats(std::ostream& out) const {
        DiffStats stats = computeStats();
        out << "=== Diff Statistics ===\n";
        out << "Additions:     " << Color::GREEN << "+" << stats.additions << Color::RESET << "\n";
        out << "Deletions:     " << Color::RED << "-" << stats.deletions << Color::RESET << "\n";
        out << "Modifications: " << Color::YELLOW << "~" << stats.modifications << Color::RESET << "\n";
        out << "Total changes: " << stats.total_changes() << "\n";
        out << "File 1 lines:  " << stats.file1_lines << "\n";
        out << "File 2 lines:  " << stats.file2_lines << "\n";
    }

    /**
     * Export stats as JSON string.
     */
    std::string statsToJson() const {
        DiffStats stats = computeStats();
        json j;
        j["file1"] = file1_path_;
        j["file2"] = file2_path_;
        j["additions"] = stats.additions;
        j["deletions"] = stats.deletions;
        j["modifications"] = stats.modifications;
        j["total_changes"] = stats.total_changes();
        j["file1_lines"] = stats.file1_lines;
        j["file2_lines"] = stats.file2_lines;
        return j.dump(2);
    }

private:
    std::string file1_path_;
    std::string file2_path_;
    int context_lines_;
    std::vector<std::string> file1_lines_;
    std::vector<std::string> file2_lines_;

    static std::vector<std::string> readFile(const std::string& path) {
        std::ifstream ifs(path);
        if (!ifs) {
            std::cerr << "Error: Cannot open file: " << path << "\n";
            std::exit(1);
        }
        std::vector<std::string> lines;
        std::string line;
        while (std::getline(ifs, line)) {
            lines.push_back(line);
        }
        return lines;
    }

    static std::string truncate(const std::string& s, int maxLen) {
        if (static_cast<int>(s.size()) > maxLen) {
            return s.substr(0, maxLen - 3) + "...";
        }
        return s;
    }

    static std::string padRight(const std::string& s, int width) {
        if (static_cast<int>(s.size()) >= width) return s.substr(0, width);
        return s + std::string(width - s.size(), ' ');
    }

    static std::string basename(const std::string& path) {
        size_t pos = path.find_last_of("/\\");
        return (pos == std::string::npos) ? path : path.substr(pos + 1);
    }

    static std::string escapeHtml(const std::string& s) {
        std::string result;
        result.reserve(s.size());
        for (char c : s) {
            switch (c) {
                case '&':  result += "&amp;";  break;
                case '<':  result += "&lt;";   break;
                case '>':  result += "&gt;";   break;
                case '"':  result += "&quot;"; break;
                default:   result += c;        break;
            }
        }
        return result;
    }
};

void printUsage() {
    std::cerr << "Usage: text_diff_tool <file1> <file2> [options]\n";
    std::cerr << "Options:\n";
    std::cerr << "  --format <unified|side-by-side|html|json|all>  Output format (default: unified)\n";
    std::cerr << "  --context <n>          Context lines (default: 3)\n";
    std::cerr << "  --html-output <path>   HTML output file path\n";
    std::cerr << "  --width <n>            Column width for side-by-side (default: 120)\n";
    std::cerr << "  --stats                Show statistics\n";
}

int main(int argc, char* argv[]) {
    if (argc < 3) {
        printUsage();
        return 1;
    }

    std::string file1 = argv[1];
    std::string file2 = argv[2];
    std::string format = "unified";
    int contextLines = 3;
    int width = 120;
    std::string htmlOutput = "diff_report.html";
    bool showStats = false;

    for (int i = 3; i < argc; i++) {
        std::string arg = argv[i];
        if (arg == "--format" && i + 1 < argc) {
            format = argv[++i];
        } else if (arg == "--context" && i + 1 < argc) {
            contextLines = std::atoi(argv[++i]);
        } else if (arg == "--html-output" && i + 1 < argc) {
            htmlOutput = argv[++i];
        } else if (arg == "--width" && i + 1 < argc) {
            width = std::atoi(argv[++i]);
        } else if (arg == "--stats") {
            showStats = true;
        }
    }

    TextDiffTool tool(file1, file2, contextLines);

    if (showStats || format == "all") {
        tool.displayStats(std::cout);
        std::cout << "\n";
    }

    if (format == "unified" || format == "all") {
        tool.displayUnified(std::cout);
    }
    if (format == "side-by-side" || format == "all") {
        if (format == "all") std::cout << "\n";
        tool.displaySideBySide(std::cout, width);
    }
    if (format == "html" || format == "all") {
        if (format == "all") std::cout << "\n";
        tool.saveHtml(htmlOutput);
    }
    if (format == "json") {
        std::cout << tool.statsToJson() << "\n";
    }

    return 0;
}