/** * Structural conformance gate. * * Counts production-source code-smells and compares against committed baselines. * Nonzero exit code = a baseline was violated. The script does NOT alter * files; it only audits and reports. * * Usage: node scripts/check-structural.js [--fix-baseline] */ import { readFileSync, writeFileSync } from 'fs' import { fileURLToPath } from 'url' import glob from 'glob' import path from 'path' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const ROOT = path.resolve(__dirname, '..') const BASELINE_PATH = path.join(ROOT, 'scripts', '.structural-baseline.json') const PACKAGE_DIRS = glob.sync('packages/*', { cwd: ROOT, absolute: true }) .filter(d => !d.match(/(imhotep-fixtures|imhotep-bench|imhotep-cli)$/)) function srcFiles(pkgDir) { return glob.sync('src/**/*.ts', { cwd: pkgDir, absolute: true }) .filter(f => !f.endsWith('.test.ts') && !f.endsWith('.d.ts')) } function countPattern(files, pattern, excludeComment = false) { let count = 0 const locations = [] for (const f of files) { const lines = readFileSync(f, 'utf8').split('\n') for (let i = 0; i < lines.length; i++) { const line = lines[i] if (excludeComment && line.trim().startsWith('//')) continue const matches = line.match(pattern) if (matches) { count += matches.length locations.push(`${path.relative(ROOT, f)}:${i + 1}`) } } } return { count, locations } } const allFiles = PACKAGE_DIRS.flatMap(d => srcFiles(d)) const pkgNames = PACKAGE_DIRS.map(d => path.basename(d)) const violations = { asAny: countPattern(allFiles, /\bas any\b/g, true), emptyCatch: countPattern(allFiles, /\bcatch\s*\{/), nullishZero: countPattern(allFiles, /\?\?\s*0(?![.\d])/, true), } const baseline = (() => { try { return JSON.parse(readFileSync(BASELINE_PATH, 'utf8')) } catch { return {} } })() const results = { timestamp: new Date().toISOString(), packages: pkgNames, violations, baseline, } const errors = [] for (const [key, current] of Object.entries(violations)) { const allowed = baseline[key] ?? 0 const delta = current.count - allowed const status = delta <= 0 ? 'PASS' : 'FAIL' const msg = ` ${key.padEnd(18)} current=${String(current.count).padStart(3)} baseline=${String(allowed).padStart(3)} delta=${String(delta).padStart(4)} ${status}` console.log(msg) if (status === 'FAIL') { errors.push(`${key} (${current.count} > baseline ${allowed})`) console.log(` New locations:`) for (const loc of current.locations.slice(0, 10)) { console.log(` ${loc}`) } if (current.locations.length > 10) { console.log(` ... and ${current.locations.length - 10} more`) } } } const fixBaseline = process.argv.includes('--fix-baseline') if (fixBaseline) { const newBaseline = {} for (const [key, v] of Object.entries(violations)) { newBaseline[key] = v.count } writeFileSync(BASELINE_PATH, JSON.stringify(newBaseline, null, 2) + '\n') console.log(`\n Baseline written to ${path.relative(ROOT, BASELINE_PATH)}`) console.log('Structural gate PASSED (baseline updated)') process.exit(0) } writeFileSync(BASELINE_PATH, JSON.stringify(baseline, null, 2) + '\n') if (errors.length > 0) { console.error(`\nStructural gate FAILED (${errors.length} violation(s)):`) for (const e of errors) console.error(` - ${e}`) console.error(`\nIf these are legitimate, update the baseline with:`) console.error(` node scripts/check-structural.js --fix-baseline`) process.exit(1) } else { console.log('\nStructural gate PASSED') }