feat: CI verification pipeline and structural conformance gates

- Add scripts/check-structural.js: counts production as any, empty catch,
  and ?? 0 patterns; fails CI if counts exceed committed baseline
- Add scripts/.structural-baseline.json: committed baseline (101 as any,
  17 empty catch, 62 ?? 0); use --fix-baseline to ratchet down
- Add scripts/ci-verify.sh: ordered pipeline (build → typecheck → lint →
  structural → unit tests → E2E) with pass/fail summary
- Add npm scripts: test:structural, test:structural:fix, ci:verify
- Add cache-staleness conformance test: verifies WORLD_CACHE_SCHEMA_VERSION
  is prefixed to all cache keys for auto-invalidation on schema changes
- Cache test suite grows from 141 → 142 passes
This commit is contained in:
John Dvorak
2026-05-22 16:33:34 -07:00
parent 3b7be0aaf0
commit 8f823d959b
5 changed files with 208 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
/**
* 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')
}