/** * cli:derive-test-data — validate.ts * * Spec validation + prerequisites. The module folder and its `entité.md` are * existence-gated (a dataset is checked AGAINST a data model — without one * there is nothing to be coherent with, and the error names the skill that * produces it). `jeu-de-test.md` is deliberately NOT a prerequisite: the * dataset is optional, its absence is a RESULT the report says out loud. */ import { existsSync, statSync } from 'node:fs' import { isAbsolute, join } from 'node:path' import { DeriveTestDataInputSchema, type ValidationResult } from './types.js' /** entité.md exists on disk under either Unicode normalization. */ export function entiteDocPath(moduleDir: string): string | null { for (const fileName of ['entité.md', 'entité.md'.normalize('NFD')]) { const p = join(moduleDir, fileName) try { if (existsSync(p) && statSync(p).isFile()) return p } catch { /* unreadable — treated as absent */ } } return null } export function validateSpec(raw: unknown, workdir?: string): ValidationResult { const parsed = DeriveTestDataInputSchema.safeParse(raw) if (!parsed.success) { return { valid: false, errors: parsed.error.issues.map((i) => `[${i.path.join('.')}] ${i.message}`), } } const spec = parsed.data const resolvedBaRoot = isAbsolute(spec.baRoot) ? spec.baRoot : join(workdir ?? process.cwd(), spec.baRoot) const errors: string[] = [] const moduleDir = join(resolvedBaRoot, spec.app, spec.module) if (!existsSync(moduleDir)) { errors.push(`Module folder not found: ${moduleDir}`) return { valid: false, errors, spec, resolvedBaRoot } } if (entiteDocPath(moduleDir) === null) { errors.push(`${spec.app}/${spec.module} has no entité.md — run /ba-create-data-model (phase 6) first.`) } return { valid: errors.length === 0, errors, spec, resolvedBaRoot } }