Implement the 031-quality-gates-hygiene feature that strengthens automated quality gates by adding pnpm audit to the check script, v8 coverage thresholds with per-directory auto-ratchet (domain 96%, adapters 71%, persistence 87%), Biome cognitive complexity enforcement (max 15), and keyboard accessibility for the combatant row, while cleaning up all blanket biome-ignore comments, refactoring 5 overly complex functions into smaller helpers, and codifying the early-enforcement principle in the constitution and CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-11 00:52:29 +01:00
parent 47da942b73
commit 69363d4f7d
19 changed files with 1265 additions and 163 deletions

View File

@@ -47,40 +47,34 @@ function matchesForbidden(importPath, forbidden) {
return importPath === forbidden || importPath.startsWith(`${forbidden}/`);
}
/** @returns {{ file: string, line: number, importPath: string, forbidden: string }[]} */
export function checkLayerBoundaries() {
const IMPORT_RE =
/(?:import|from)\s+["']([^"']+)["']|require\s*\(\s*["']([^"']+)["']\s*\)/;
/**
* Check a single file for forbidden imports.
* @param {string} file
* @param {string[]} forbidden
* @returns {{ file: string, line: number, importPath: string, forbidden: string }[]}
*/
function checkFile(file, forbidden) {
/** @type {{ file: string, line: number, importPath: string, forbidden: string }[]} */
const violations = [];
const content = readFileSync(file, "utf-8");
const lines = content.split("\n");
for (const [srcDir, forbidden] of Object.entries(FORBIDDEN)) {
const absDir = join(ROOT, srcDir);
let files;
try {
files = collectTsFiles(absDir);
} catch {
continue;
}
for (let i = 0; i < lines.length; i++) {
const match = lines[i].match(IMPORT_RE);
if (!match) continue;
for (const file of files) {
const content = readFileSync(file, "utf-8");
const lines = content.split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const match = line.match(
/(?:import|from)\s+["']([^"']+)["']|require\s*\(\s*["']([^"']+)["']\s*\)/,
);
if (!match) continue;
const importPath = match[1] || match[2];
for (const f of forbidden) {
if (matchesForbidden(importPath, f)) {
violations.push({
file: relative(ROOT, file),
line: i + 1,
importPath,
forbidden: f,
});
}
}
const importPath = match[1] || match[2];
for (const f of forbidden) {
if (matchesForbidden(importPath, f)) {
violations.push({
file: relative(ROOT, file),
line: i + 1,
importPath,
forbidden: f,
});
}
}
}
@@ -88,6 +82,30 @@ export function checkLayerBoundaries() {
return violations;
}
/**
* Check all files in a layer directory for forbidden imports.
* @param {string} srcDir
* @param {string[]} forbidden
* @returns {{ file: string, line: number, importPath: string, forbidden: string }[]}
*/
function checkLayer(srcDir, forbidden) {
const absDir = join(ROOT, srcDir);
let files;
try {
files = collectTsFiles(absDir);
} catch {
return [];
}
return files.flatMap((file) => checkFile(file, forbidden));
}
/** @returns {{ file: string, line: number, importPath: string, forbidden: string }[]} */
export function checkLayerBoundaries() {
return Object.entries(FORBIDDEN).flatMap(([srcDir, forbidden]) =>
checkLayer(srcDir, forbidden),
);
}
// Run as CLI if invoked directly
if (
process.argv[1] &&

View File

@@ -27,29 +27,28 @@ const OUTPUT_PATH = join(PROJECT_ROOT, "data/bestiary/index.json");
// --- Build source display name map from books.json + adventures.json ---
/** Populate map from a list of entries that have source/id + name. */
function addEntriesToMap(map, entries) {
for (const entry of entries) {
if (!entry.name) continue;
if (entry.source) {
map[entry.source] = entry.name;
}
// Some entries use "id" instead of "source"
if (entry.id && !map[entry.id]) {
map[entry.id] = entry.name;
}
}
}
function buildSourceMap() {
const map = {};
const books = JSON.parse(readFileSync(BOOKS_PATH, "utf-8"));
for (const book of books.book ?? []) {
if (book.source && book.name) {
map[book.source] = book.name;
}
// Some books use "id" instead of "source"
if (book.id && book.name && !map[book.id]) {
map[book.id] = book.name;
}
}
addEntriesToMap(map, books.book ?? []);
const adventures = JSON.parse(readFileSync(ADVENTURES_PATH, "utf-8"));
for (const adv of adventures.adventure ?? []) {
if (adv.source && adv.name) {
map[adv.source] = adv.name;
}
if (adv.id && adv.name && !map[adv.id]) {
map[adv.id] = adv.name;
}
}
addEntriesToMap(map, adventures.adventure ?? []);
// Manual additions for sources missing from books.json / adventures.json
const manual = {