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 += "&"; break;
case '<': result += "<"; break;
case '>': result += ">"; break;
case '"': result += """; 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;
}