#!/usr/bin/env node /** * cli:derive-lifecycle — entry point. * * BACKFILL: derive the first-order `lifecycle` block of form pagespecs * (lib/page-spec-lifecycle.ts) from anchors ALREADY authored — the entity's * status enum (entité.md) + the workflow custom actions (workflowTransition + * payloadParameters). Never invents a phase (lexicon candidates → reported * `needs-judgment` for the /ui-design rubric §6 pass), never touches an * existing `lifecycle`. Idempotent. * * Invocation: * npx --prefer-offline tsx skills/business-analyse/create-prd/cli/derive-lifecycle/index.ts \ * --spec '{"mode":"derive","pagespecDir":".smartstack/ba/CRM/FACTURATION/pagespecs","moduleRoot":".smartstack/ba/CRM/FACTURATION"}' \ * [--workdir ] * * `mode:"check"` is the deterministic engine of PRD-120 — it validates every * pagespec CARRYING a lifecycle block and reports findings, writing nothing. * Runbook: THIS (derive) → /ba-audit-prd (PRD-120/121) → frontend regen via * the spec-drift the write itself triggers (compute-page-diff); backend regen * (Create-honnête phase exclusion) follows the same drift. */ import { parseArgs } from 'node:util' import { generateEnvelope, failGenerate, printEnvelope } from '../../../../lib/output.js' import { validate } from './validate.js' import { execute } from './execute.js' import type { DeriveLifecycleSpec } from './types.js' const COMMAND = 'derive-lifecycle' function main(): void { const { values } = parseArgs({ options: { spec: { type: 'string' }, workdir: { type: 'string' }, }, strict: true, }) if (!values.spec) { printEnvelope(failGenerate(COMMAND, ['--spec is required'])) process.exit(1) } let raw: unknown try { raw = JSON.parse(values.spec) } catch { printEnvelope(failGenerate(COMMAND, ['Invalid JSON'])) process.exit(1) } const validation = validate(raw) if (!validation.valid) { printEnvelope(failGenerate(COMMAND, validation.errors)) process.exit(1) } try { const spec = raw as DeriveLifecycleSpec const report = execute(spec, values.workdir ?? process.cwd()) printEnvelope(generateEnvelope(COMMAND, { data: report, filesModified: report.outcomes.filter((o) => o.status === 'derived' && spec.mode === 'derive').map((o) => o.path), nextSteps: spec.mode === 'check' ? [ ...(report.findings.length > 0 ? ['Fix the PRD-120 findings in the pagespecs, then re-run the check'] : ['Lifecycle blocks are coherent — nothing to fix']), ] : [ 'Run /ba-audit-prd (PRD-120/121) on the module', 'Regenerate the entity pages — the pagespec write is a legitimate spec-drift (compute-page-diff)', ...(report.outcomes.some((o) => o.status === 'needs-judgment') ? ['Run the /ui-design rubric §6 pass on the needs-judgment pagespecs — no phase is invented mechanically'] : []), ], })) } catch (e) { printEnvelope(failGenerate(COMMAND, [e instanceof Error ? e.message : String(e)])) process.exit(1) } } main()