#!/usr/bin/env node /** * cli:derive-kanban-spec — entry point. * * BACKFILL: fold the section's SmartKanban screen (screen.md) into the LIST * pagespec as the first-order `kanban` block (lib/page-spec-kanban.ts) — the * exact mirror of the SmartCard/cards folding (PRD-117) — and project the * module's workflow rules (Flow transitions, lib/ba-rules-rows) into * `kanban.transitions[]`. Never invents (unmappable anchors → reported * `needs-judgment`), never touches an existing `kanban`. Idempotent. * * Invocation: * npx --prefer-offline tsx skills/business-analyse/create-prd/cli/derive-kanban-spec/index.ts \ * --spec '{"mode":"derive","pagespecDir":".smartstack/ba/HR/LEAVE/pagespecs","moduleRoot":".smartstack/ba/HR/LEAVE"}' \ * [--workdir ] * * `mode:"check"` is the deterministic engine of PRD-135 — it validates every * pagespec CARRYING a kanban block (+ the legacy standalone gate and the * viewModes coherence), reporting findings, writing nothing. * Runbook: THIS (derive) → /ba-audit-prd (PRD-135) → frontend regen via the * spec-drift the write itself triggers (compute-page-diff); the backend move * guard (scaffold-business transition matrix) 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 { DeriveKanbanSpec } from './types.js' const COMMAND = 'derive-kanban-spec' 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 DeriveKanbanSpec const report = execute(spec, values.workdir ?? process.cwd()) printEnvelope(generateEnvelope(COMMAND, { data: report, filesModified: spec.mode === 'derive' ? report.outcomes.filter((o) => o.status === 'derived').map((o) => o.path) : [], nextSteps: spec.mode === 'check' ? [ ...(report.findings.length > 0 ? ['Fix the PRD-135 findings in the pagespecs, then re-run the check'] : ['Kanban blocks are coherent — nothing to fix']), ] : [ 'Run /ba-audit-prd (PRD-135) on the module', 'Regenerate the entity list pages — the pagespec write is a legitimate spec-drift (compute-page-diff)', ...(report.outcomes.some((o) => (o.needsTranslation?.length ?? 0) > 0) ? ['Run /ba-translate-prd — column labels were seeded with the fr copy in en/it/de'] : []), ...(report.outcomes.some((o) => o.status === 'needs-judgment') ? ['Resolve the needs-judgment pagespecs on the BA side — nothing is invented mechanically'] : []), ...(report.outcomes.some((o) => o.status === 'legacy-standalone') ? ['Delete the legacy standalone kanban pagespecs once their list sibling carries the block'] : []), ], })) } catch (e) { printEnvelope(failGenerate(COMMAND, [e instanceof Error ? e.message : String(e)])) process.exit(1) } } main()