#!/usr/bin/env node /** * cli:derive-fk-specs — entry point. * * Deterministically derives every FK field's `fkTo` block from a module's * `entité.md` Rel: lines, so ba-develop never hand-derives them (the silent-drop * bug behind "lookups show the raw Guid" — an un-derived FK fell back to a * free-text Guid `` with no error). The orchestrator splices each * entity's `fields[].fkTo` VERBATIM into scaffold-component / scaffold-api-client. * * Invocation: * npx --prefer-offline tsx skills/ba-develop/cli/derive-fk-specs/index.ts \ * --spec '{"moduleRoot":".smartstack/ba//"}' * * Exit code: always 0 on a successful derivation — a module without FKs is a * valid empty answer. `unresolved[]` non-empty is the BLOCKING signal: * scaffold-component's FK gate refuses the un-enriched field anyway, so fix the * BA docs instead of scaffolding through. */ import { parseArgs } from 'node:util' import { validate } from './validate.js' import { deriveFkSpecs } from './derive.js' import { executeEnvelope, failExecute, printEnvelope } from '../../../lib/output.js' import type { DeriveFkSpecsReport } from './types.js' const COMMAND = 'derive-fk-specs' function main(): void { const { values } = parseArgs({ options: { spec: { type: 'string' } }, strict: true }) if (!values.spec) { printEnvelope(failExecute(COMMAND, ['--spec is required'])) process.exit(1) } let raw: unknown try { raw = JSON.parse(values.spec) } catch { printEnvelope(failExecute(COMMAND, ['Invalid JSON in --spec'])) process.exit(1) } const validation = validate(raw) if (!validation.valid || !validation.spec) { printEnvelope(failExecute(COMMAND, validation.errors)) process.exit(1) } const report = deriveFkSpecs(validation.spec) const nextSteps: string[] = [ "Phase 3a: splice each entity's fields[].fkTo VERBATIM into the scaffold-component / scaffold-api-client field entries (match by field name, case-insensitive on the first letter). Use `required` for the field's required flag and `role` (when present) as the field label.", 'Then run the coverage gates — audit-dev-frontend DEV-UI-022/033. A FK-shaped field without fkTo now hard-fails scaffold-component validation (noLookup:true is the explicit opt-out for genuine non-FK identifiers).', ] // An unresolved FK is NOT a silent skip: scaffolding the source entity anyway // ships a raw-Guid field (form input, list column, detail dd). Surface it // loudly — fix the BA tree (create the target screen/pagespec or repair the // Rel: line), never hand-patch the generated page. if (report.unresolved.length > 0) { nextSteps.unshift( `BLOCKING — ${report.unresolved.length} FK(s) could not be resolved to a lookup route: ` + report.unresolved.map((u) => `${u.entity}.${u.field} → ${u.target} (${u.reason})`).join(' ; ') + '. Do NOT scaffold these fields without fkTo.', ) } printEnvelope( executeEnvelope(COMMAND, { success: true, data: { entities: report.totals.entities, fks: report.totals.fks, unresolved: report.totals.unresolved, }, report, warnings: report.warnings, nextSteps, }), ) process.exit(0) } main()