import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { fail, pass, repositoryRoot } from './lib.mjs'; function parseCsv(text) { const rows = []; let row = []; let field = ''; let quoted = false; for (let index = 0; index < text.length; index += 1) { const character = text[index]; if (quoted) { if (character === '"' && text[index + 1] === '"') { field += '"'; index += 1; } else if (character === '"') { quoted = false; } else { field += character; } } else if (character === '"') { quoted = true; } else if (character === ',') { row.push(field); field = ''; } else if (character === '\n') { row.push(field.replace(/\r$/, '')); rows.push(row); row = []; field = ''; } else { field += character; } } if (quoted) throw new Error('Unterminated CSV quote.'); if (field || row.length) { row.push(field.replace(/\r$/, '')); rows.push(row); } return rows; } const failures = []; for (const file of [ 'docs/IMPLEMENTATION_ISSUE_REGISTER_v1.4.csv', 'docs/quality/PHASE_14_MANUAL_QA_RESULTS_v1.0.csv', ]) { const rows = parseCsv(await readFile(path.join(repositoryRoot, file), 'utf8')); const expected = rows[0]?.length ?? 0; for (const [index, row] of rows.entries()) { if (row.length !== expected) { failures.push(`${file}:${index + 1} has ${row.length} columns; expected ${expected}.`); } } } const contentType = await readFile( path.join(repositoryRoot, 'src/lib/security/content-type.ts'), 'utf8', ); const route = await readFile(path.join(repositoryRoot, 'src/app/api/demo/route.ts'), 'utf8'); if (!contentType.includes("=== 'application/json'")) { failures.push('Exact JSON media-type comparison is missing.'); } if (!route.includes('isApplicationJson(contentType)')) { failures.push('Demo API does not use the exact JSON media-type helper.'); } if (route.includes("startsWith('application/json')")) { failures.push('Demo API still accepts look-alike JSON media types by prefix.'); } const componentVisual = await readFile( path.join(repositoryRoot, 'tests/visual/component-system.visual.spec.ts'), 'utf8', ); const integrationVisual = await readFile( path.join(repositoryRoot, 'tests/visual/integrations.visual.spec.ts'), 'utf8', ); if (!componentVisual.includes('hpc.theme.preference')) { failures.push('Component visual test does not use the canonical theme storage key.'); } if (componentVisual.includes('rdg-theme-preference')) { failures.push('Component visual test still uses the stale theme storage key.'); } for (const [name, text] of [ ['component-system.visual.spec.ts', componentVisual], ['integrations.visual.spec.ts', integrationVisual], ]) { if (!text.includes("project.name !== 'chromium'")) { failures.push(`${name} is not restricted to canonical Chromium snapshots.`); } } const workflow = await readFile(path.join(repositoryRoot, '.github/workflows/ci.yml'), 'utf8'); for (const command of [ 'pnpm test:integration', 'pnpm test:security', 'pnpm test:privacy', 'pnpm test:browser', 'pnpm test:a11y', 'pnpm test:visual', ]) { if (!workflow.includes(command)) failures.push(`CI omits required command: ${command}`); } if (failures.length) fail(failures); else pass( 'Phase 15 evidence CSVs, exact media-type control, deterministic visual setup, and CI test gates are present.', );