/** * cli:derive-uc-coverage — validate.ts * * Spec validation + prerequisites: the module folder must exist and carry at * least one `use-case.md` — an empty report must mean "no UC to cover", * never "inputs absent". `pagespecs/` is OPTIONAL (the PRD leg reports * `no-pagespecs` loudly instead of failing — SCR-024 runs before the PRD * exists). */ import { existsSync, readdirSync } from 'node:fs' import { isAbsolute, join } from 'node:path' import { DeriveUcCoverageInputSchema, type ValidationResult } from './types.js' function hasUseCaseDoc(dir: string, depth: number): boolean { if (depth > 3) return false if (existsSync(join(dir, 'use-case.md'))) return true try { // Same skip-set as index.ts's collectDocs — the old `/^[a-z]/` filter // was the fail-open twin index.ts already removed: an uppercase/digit/ // accented section folder made the prerequisite claim « no use-case.md » // while collectDocs would have read it. return readdirSync(dir, { withFileTypes: true }).some( (e) => e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.') && e.name !== 'pagespecs' && e.name !== 'node_modules' && hasUseCaseDoc(join(dir, e.name), depth + 1), ) } catch { return false } } export function validateSpec(raw: unknown, workdir?: string): ValidationResult { const parsed = DeriveUcCoverageInputSchema.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 (!hasUseCaseDoc(moduleDir, 0)) { errors.push( `${spec.app}/${spec.module} has no use-case.md (module or section) — run /ba-create-use-case (phase 3) first.`, ) } return { valid: errors.length === 0, errors, spec, resolvedBaRoot } }