Spell Checker (javascript, written by Claude Code)
envgap__claude-code__javascript-t1-31
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 #31 · 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: Spell Checker Write a program that checks spelling in text files using dictionary lookups and edit distance algorithms, supporting custom dictionaries, multiple languages, and suggestion ranking. FUNCTIONAL REQUIREMENTS: - Accept a text file path as a command-line argument - Load a dictionary of valid words (ship a built-in English dictionary of at least 50,000 common words) - Identify misspelled words by checking each word against the dictionary, ignoring case, numbers, URLs, email addresses, and common abbreviations - Generate spelling suggestions for each misspelled word using edit distance (Levenshtein distance) with up to 2 edits, ranked by likelihood (frequency-weighted if possible) - Support custom dictionaries via --dictionary flag (path to a text file with one word per line) that supplements the built-in dictionary - Support an ignore list via --ignore flag (comma-separated words or path to file) for domain-specific terms that should not be flagged - Support multiple output modes via --format flag: interactive (show each error with context and suggestions), report (summary with all errors), and json (machine-readable) - Show each misspelled word with its line number, column number, and surrounding context (the line containing the error with the word highlighted) - Compute document statistics: total words, unique words, misspelled words count, and spelling accuracy percentage - Support checking multiple files via --batch flag - Save the spell check report as JSON with --output flag (default: spelling_report.json) including all misspelled words, their locations, and suggested corrections - If no input file is given, generate a sample text document with intentional spelling errors of various types (transpositions, missing letters, extra letters, wrong letters), check it, and display the results with suggestions - Handle errors: unreadable files, binary file detection, encoding issues, and very large 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.
package.json
{
"name": "spell-checker",
"version": "1.0.0",
"description": "Spell checker using dictionary lookups and Levenshtein distance with custom dictionary support",
"main": "spellcheck.js",
"scripts": {
"start": "node spellcheck.js"
},
"keywords": ["spellcheck", "levenshtein", "dictionary", "nspell"],
"license": "MIT",
"dependencies": {
"nspell": "2.1.5",
"chalk": "4.1.2",
"dictionary-en": "^3.0.0"
}
}
spellcheck.js
/**
* Spell Checker - Checks spelling via dictionary lookups and Levenshtein distance.
* Supports custom dictionaries and provides spelling suggestions.
*
* Dependencies:
* - nspell (2.1.5): Hunspell-compatible spell checking for Node.js
* - chalk (4.1.2): Terminal string styling for colored output
*/
const nspell = require("nspell");
const chalk = require("chalk");
const fs = require("fs");
const path = require("path");
const readline = require("readline");
// Load English dictionary for nspell
const affPath = path.join(
__dirname,
"node_modules",
"dictionary-en",
"index.aff"
);
const dicPath = path.join(
__dirname,
"node_modules",
"dictionary-en",
"index.dic"
);
/**
* Custom dictionary manager with JSON persistence.
*/
class CustomDictionary {
constructor(filePath) {
this.filePath = filePath || path.join(__dirname, "custom_dictionary.json");
this.words = new Set();
this._load();
}
_load() {
try {
if (fs.existsSync(this.filePath)) {
const data = JSON.parse(fs.readFileSync(this.filePath, "utf-8"));
if (data.words && Array.isArray(data.words)) {
data.words.forEach((w) => this.words.add(w.toLowerCase()));
}
}
} catch (err) {
// Start with empty dictionary on error
}
}
save() {
const data = { words: Array.from(this.words).sort() };
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), "utf-8");
}
add(word) {
this.words.add(word.toLowerCase());
this.save();
}
remove(word) {
this.words.delete(word.toLowerCase());
this.save();
}
contains(word) {
return this.words.has(word.toLowerCase());
}
list() {
return Array.from(this.words).sort();
}
}
/**
* Calculate Levenshtein distance between two strings.
*/
function levenshteinDistance(a, b) {
const m = a.length;
const n = b.length;
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
for (let i = 0; i <= m; i++) dp[i][0] = i;
for (let j = 0; j <= n; j++) dp[0][j] = j;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
dp[i][j] = Math.min(
dp[i - 1][j] + 1, // deletion
dp[i][j - 1] + 1, // insertion
dp[i - 1][j - 1] + cost // substitution
);
}
}
return dp[m][n];
}
/**
* Spell checking engine using nspell with custom dictionary support.
*/
class SpellCheckEngine {
constructor(customDictPath) {
this.customDict = new CustomDictionary(customDictPath);
this.spell = null;
}
async initialize() {
try {
const aff = fs.readFileSync(affPath, "utf-8");
const dic = fs.readFileSync(dicPath, "utf-8");
this.spell = nspell(aff, dic);
// Add custom dictionary words to nspell
for (const word of this.customDict.words) {
this.spell.add(word);
}
} catch (err) {
// Try loading dictionary-en via require
try {
const dictionary = require("dictionary-en");
await new Promise((resolve, reject) => {
dictionary((err, dict) => {
if (err) reject(err);
this.spell = nspell(dict);
for (const word of this.customDict.words) {
this.spell.add(word);
}
resolve();
});
});
} catch (loadErr) {
console.error(
chalk.red("Error: Could not load English dictionary."),
chalk.yellow(
"Install it with: npm install dictionary-en"
)
);
process.exit(1);
}
}
}
addToDictionary(word) {
this.customDict.add(word);
if (this.spell) {
this.spell.add(word.toLowerCase());
}
}
removeFromDictionary(word) {
this.customDict.remove(word);
if (this.spell) {
this.spell.remove(word.toLowerCase());
}
}
checkWord(word) {
const cleanWord = word.trim().toLowerCase();
if (!cleanWord || !/^[a-z]+$/i.test(cleanWord)) {
return { word, correct: true, suggestions: [], correction: word };
}
// Check custom dictionary
if (this.customDict.contains(cleanWord)) {
return { word, correct: true, suggestions: [], correction: word };
}
if (this.spell && this.spell.correct(cleanWord)) {
return { word, correct: true, suggestions: [], correction: word };
}
// Get suggestions from nspell
let suggestions = [];
if (this.spell) {
suggestions = this.spell.suggest(cleanWord).slice(0, 10);
}
// Sort suggestions by Levenshtein distance
suggestions.sort(
(a, b) =>
levenshteinDistance(cleanWord, a.toLowerCase()) -
levenshteinDistance(cleanWord, b.toLowerCase())
);
const correction = suggestions.length > 0 ? suggestions[0] : word;
return {
word,
correct: false,
suggestions,
correction,
};
}
checkText(text) {
const words = text.split(/\s+/);
const errors = [];
const correctedWords = [];
let errorCount = 0;
for (const word of words) {
const stripped = word.replace(/^[^a-zA-Z]+|[^a-zA-Z]+$/g, "").toLowerCase();
if (!stripped) {
correctedWords.push(word);
continue;
}
const result = this.checkWord(stripped);
if (!result.correct) {
errorCount++;
errors.push(result);
// Preserve surrounding punctuation
const prefixMatch = word.match(/^([^a-zA-Z]*)/);
const suffixMatch = word.match(/([^a-zA-Z]*)$/);
const prefix = prefixMatch ? prefixMatch[1] : "";
const suffix = suffixMatch ? suffixMatch[1] : "";
correctedWords.push(prefix + result.correction + suffix);
} else {
correctedWords.push(word);
}
}
const totalWords = words.filter((w) => /[a-zA-Z]/.test(w)).length;
const accuracy =
totalWords > 0
? Math.round((1 - errorCount / totalWords) * 10000) / 100
: 100;
return {
original_text: text,
corrected_text: correctedWords.join(" "),
errors,
statistics: {
total_words: totalWords,
misspelled_words: errorCount,
accuracy,
},
};
}
suggestSimilar(word, maxDistance = 2) {
const result = this.checkWord(word);
const scored = [];
for (const suggestion of result.suggestions) {
const dist = levenshteinDistance(
word.toLowerCase(),
suggestion.toLowerCase()
);
if (dist <= maxDistance) {
scored.push({ suggestion, distance: dist });
}
}
scored.sort((a, b) => a.distance - b.distance);
return scored;
}
}
/**
* Run interactive spell checker mode.
*/
async function interactiveMode(engine) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log(chalk.bold("=".repeat(60)));
console.log(chalk.bold.blue(" Spell Checker - Interactive Mode"));
console.log(chalk.bold("=".repeat(60)));
console.log();
console.log("Commands:");
console.log(" " + chalk.green("check <text>") + " - Check spelling of text");
console.log(" " + chalk.green("suggest <word>") + " - Get suggestions for a word");
console.log(" " + chalk.green("add <word>") + " - Add word to custom dictionary");
console.log(" " + chalk.green("remove <word>") + " - Remove word from custom dictionary");
console.log(" " + chalk.green("list") + " - List custom dictionary words");
console.log(
" " + chalk.green("distance <w1> <w2>") + " - Calculate Levenshtein distance"
);
console.log(" " + chalk.green("quit") + " - Exit the spell checker");
console.log("-".repeat(60));
const prompt = () => {
rl.question(chalk.cyan("\n> "), (input) => {
input = input.trim();
if (!input) {
prompt();
return;
}
const spaceIdx = input.indexOf(" ");
const command = spaceIdx >= 0 ? input.substring(0, spaceIdx).toLowerCase() : input.toLowerCase();
const rest = spaceIdx >= 0 ? input.substring(spaceIdx + 1).trim() : "";
switch (command) {
case "quit":
case "exit":
console.log("Goodbye!");
rl.close();
return;
case "check": {
if (!rest) {
console.log("Usage: check <text>");
break;
}
const result = engine.checkText(rest);
console.log(
"\n" + chalk.white("Original: ") + result.original_text
);
console.log(
chalk.white("Corrected: ") + chalk.green(result.corrected_text)
);
const stats = result.statistics;
console.log(
chalk.white("Statistics: ") +
`${stats.total_words} words, ${stats.misspelled_words} errors, ${stats.accuracy}% accuracy`
);
if (result.errors.length > 0) {
console.log(chalk.yellow("\nMisspelled words:"));
for (const err of result.errors) {
const sugs = err.suggestions.slice(0, 5).join(", ");
console.log(
` ${chalk.red("'" + err.word + "'")} -> suggestions: [${chalk.green(sugs)}]`
);
}
}
break;
}
case "suggest": {
if (!rest) {
console.log("Usage: suggest <word>");
break;
}
const suggestions = engine.suggestSimilar(rest, 3);
if (suggestions.length > 0) {
console.log(`Suggestions for '${rest}':`);
for (const entry of suggestions) {
console.log(
` ${chalk.green(entry.suggestion)} (distance: ${entry.distance})`
);
}
} else {
console.log(chalk.yellow(`No suggestions found for '${rest}'.`));
}
break;
}
case "add":
if (!rest) {
console.log("Usage: add <word>");
break;
}
engine.addToDictionary(rest);
console.log(chalk.green(`Added '${rest}' to custom dictionary.`));
break;
case "remove":
if (!rest) {
console.log("Usage: remove <word>");
break;
}
engine.removeFromDictionary(rest);
console.log(chalk.yellow(`Removed '${rest}' from custom dictionary.`));
break;
case "list": {
const words = engine.customDict.list();
if (words.length > 0) {
console.log(`Custom dictionary (${words.length} words):`);
for (const w of words) {
console.log(` ${chalk.blue(w)}`);
}
} else {
console.log("Custom dictionary is empty.");
}
break;
}
case "distance": {
const distParts = rest.split(/\s+/);
if (distParts.length < 2) {
console.log("Usage: distance <word1> <word2>");
break;
}
const dist = levenshteinDistance(distParts[0], distParts[1]);
console.log(
`Levenshtein distance between '${distParts[0]}' and '${distParts[1]}': ${chalk.bold(String(dist))}`
);
break;
}
default:
console.log(
chalk.red(`Unknown command: '${command}'. Type a command or 'quit'.`)
);
}
prompt();
});
};
prompt();
}
/**
* Main entry point.
*/
async function main() {
const customDictPath = process.argv[2] || null;
const engine = new SpellCheckEngine(customDictPath);
await engine.initialize();
if (process.stdin.isTTY) {
await interactiveMode(engine);
} else {
// Batch mode: read from stdin and output JSON
let input = "";
process.stdin.setEncoding("utf-8");
process.stdin.on("data", (chunk) => {
input += chunk;
});
process.stdin.on("end", () => {
const text = input.trim();
if (text) {
const result = engine.checkText(text);
console.log(JSON.stringify(result, null, 2));
}
});
}
}
main().catch((err) => {
console.error("Error:", err.message);
process.exit(1);
});