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