/** * llmindex.io — CI check: mandatory author header on all source files (§4) * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * License: Proprietary — © Simon-Pierre Boucher, all rights reserved */ import { readFileSync, readdirSync, statSync } from 'node:fs'; import { join, extname } from 'node:path'; const ROOT = new URL('..', import.meta.url).pathname; const SCAN_DIRS = ['apps', 'packages', 'infra']; const EXTENSIONS = new Set(['.ts', '.tsx', '.py', '.sh', '.sql', '.mjs', '.cjs']); const EXCLUDED_DIRS = new Set([ 'node_modules', '.next', 'dist', '.turbo', '__pycache__', '.venv', '.pytest_cache', 'migrations', 'playwright-report', 'test-results', 'generated', ]); const EXCLUDED_FILES = new Set(['next-env.d.ts']); const REQUIRED = ['Author: Simon-Pierre Boucher', 'contact@spboucher.ai']; function* walk(dir) { for (const entry of readdirSync(dir)) { const full = join(dir, entry); const st = statSync(full); if (st.isDirectory()) { if (!EXCLUDED_DIRS.has(entry)) yield* walk(full); } else if (EXTENSIONS.has(extname(entry)) && !EXCLUDED_FILES.has(entry)) { yield full; } } } const failures = []; for (const dir of SCAN_DIRS) { for (const file of walk(join(ROOT, dir))) { const head = readFileSync(file, 'utf8').slice(0, 600); if (!REQUIRED.every((needle) => head.includes(needle))) { failures.push(file.replace(ROOT, '')); } } } if (failures.length > 0) { console.error(`✗ ${failures.length} file(s) missing the mandatory author header:`); for (const f of failures) console.error(` - ${f}`); process.exit(1); } console.log('✓ all source files carry the author header');