This file is a merged representation of the entire codebase, combined into a single document by Repomix.

================================================================ File Summary

Purpose:

This file contains a packed representation of the entire repository's contents. It is designed to be easily consumable by AI systems for analysis, code review, or other automated processes.

File Format:

The content is organized as follows:

  1. This summary section
  2. Repository information
  3. Directory structure
  4. Multiple file entries, each consisting of: a. A separator line (================) b. The file path (File: path/to/file) c. Another separator line d. The full contents of the file e. A blank line

Usage Guidelines:

Notes:

Additional Info:

================================================================ Directory Structure

manual.csv new-run.mjs package.json prompt.md README.md report.mjs scan.mjs stats.mjs

================================================================ Files

================ File: manual.csv

model,run,source,rule,wcag,impact,nodes,description

================ File: new-run.mjs

import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { homedir } from 'node:os';

const model = process.argv[2]; if (!model) { console.error('Usage: node new-run.mjs '); process.exit(1); }

const userClaudeDir = ${homedir().replaceAll('\\', '/')}/.claude; const readJson = path => existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : {}; const listNames = path => existsSync(path) ? readdirSync(path).map(name => name.replace(/.md$/, '')) : [];

const userPlugins = Object.keys(readJson(${userClaudeDir}/settings.json).enabledPlugins ?? {}); const userSkills = [...listNames(${userClaudeDir}/skills), ...listNames(${userClaudeDir}/commands)];

const settings = { claudeMdExcludes: [${userClaudeDir}/CLAUDE.md], enabledPlugins: Object.fromEntries(userPlugins.map(plugin => [plugin, false])), skillOverrides: Object.fromEntries(userSkills.map(skill => [skill, 'off'])), disableAllHooks: true, autoMemoryEnabled: false, }; mkdirSync(out/${model}, { recursive: true }); const runs = readdirSync(out/${model}).map(Number).filter(Number.isInteger); const runPath = out/${model}/${Math.max(0, ...runs) + 1}; mkdirSync(${runPath}/.claude, { recursive: true }); writeFileSync(${runPath}/.claude/settings.json, JSON.stringify(settings, null, '\t')); console.log(runPath);

================ File: package.json

{ "name": "claudebench", "private": true, "dependencies": { "@axe-core/playwright": "^4.14.0", "playwright-core": "^1.65.0" } }

================ File: prompt.md

Build a single-page website for a fictional community library called Maplewood Library. Put it in this folder with index.html as the entry file. It must work when index.html is opened directly from disk, with no build step and no server. Include:

Make it look polished and modern. Make your own image files or use inline SVG.

================ File: README.md

claudebench

Measures how accessible the web pages that Claude Code builds are when the prompt does not mention accessibility. Pages are checked against WCAG 2.1 A and AA with axe-core.

Setup

Requires Node.js 22 or later and Microsoft Edge.

npm install

Usage

  1. Run node new-run.mjs <model> to create the next run folder. The folder gets a .claude/settings.json that turns off your ~/.claude/CLAUDE.md, plugins, skills, hooks, and auto memory.
  2. Open that folder in VS Code, select the model in a new Claude Code chat, and paste prompt.md.
  3. Add issues you find by hand to manual.csv.
  4. Run node scan.mjs, then node report.mjs, to write results.csv.
  5. Run node stats.mjs to see violation totals for each model.

================ File: report.mjs

import { globSync, readFileSync, writeFileSync } from 'node:fs';

const HEADER = 'model,run,source,rule,wcag,impact,nodes,description'; const quote = value => "${String(value).replaceAll('"', '""')}"; const toCriterion = tag => tag.match(/^wcag(\d)(\d)(\d+)$/)?.slice(1).join('.'); const axeRows = globSync('results//.json').flatMap(file => { const [, model, fileName] = file.split(/[\/]/); const run = fileName.replace('.json', ''); const { violations } = JSON.parse(readFileSync(file, 'utf8')); return violations.map(rule => [ model, run, 'axe', rule.id, rule.tags.map(toCriterion).filter(Boolean).join(' '), rule.impact, rule.nodes.length, rule.help, ].map(quote).join(',')); }); const manualRows = readFileSync('manual.csv', 'utf8').trim().split(/\r?\n/).slice(1); writeFileSync('results.csv', [HEADER, ...axeRows, ...manualRows].join('\n') + '\n'); console.log(results.csv: ${axeRows.length} axe rows, ${manualRows.length} manual rows);

================ File: scan.mjs

import { globSync, mkdirSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { chromium } from 'playwright-core'; import AxeBuilder from '@axe-core/playwright';

const WCAG_21_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']; const browser = await chromium.launch({ channel: 'msedge' }); for (const pagePath of globSync('out///index.html')) { const [, model, run] = pagePath.split(/[\/]/); const context = await browser.newContext(); const page = await context.newPage(); await page.goto(pathToFileURL(resolve(pagePath)).href); const results = await new AxeBuilder({ page }).withTags(WCAG_21_TAGS).analyze(); await context.close(); mkdirSync(results/${model}, { recursive: true }); writeFileSync(results/${model}/${run}.json, JSON.stringify(results, null, '\t')); console.log(${model}/${run}: ${results.violations.length} violated rules); } await browser.close();

================ File: stats.mjs

import { globSync, readFileSync } from 'node:fs';

const IMPACTS = ['critical', 'serious', 'moderate', 'minor']; const stats = {}; const statsFor = model => stats[model] ??= { runs: 0, violations: 0, elements: 0, ...Object.fromEntries(IMPACTS.map(impact => [impact, 0])), };

const parseCsvLine = line => [...line.matchAll(/(?:^|,)("(?:[^"]|"")"|[^,])/g)] .map(([, cell]) => cell.replace(/^"(.)"$/, '$1').replaceAll('""', '"')); for (const file of globSync('results//*.json')) { statsFor(file.split(/[\/]/)[1]).runs++; } const rows = readFileSync('results.csv', 'utf8').trim().split(/\r?\n/).slice(1).map(parseCsvLine); for (const [model, , , , , impact, nodes] of rows) { const modelStats = statsFor(model); modelStats.violations++; modelStats.elements += Number(nodes); modelStats[impact]++; }

console.table(stats);

================================================================ End of Codebase