#!/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()