#!/usr/bin/env node import fs from 'node:fs'; import path from 'node:path'; const DEFAULT_RECEIPT_DIR = path.resolve(process.cwd(), 'state/approved-plan-continuity'); function parseArgs(argv) { let inputPath = null; let compact = false; let receiptDir = DEFAULT_RECEIPT_DIR; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; if (arg === '--input') { inputPath = argv[i + 1] ?? null; i += 1; continue; } if (arg.startsWith('--input=')) { inputPath = arg.slice('--input='.length); continue; } if (arg === '--receipt-dir') { receiptDir = argv[i + 1] ? path.resolve(argv[i + 1]) : receiptDir; i += 1; continue; } if (arg.startsWith('--receipt-dir=')) { receiptDir = path.resolve(arg.slice('--receipt-dir='.length)); continue; } if (arg === '--compact') { compact = true; continue; } } return { inputPath, compact, receiptDir }; } function readInput(inputPath) { if (!inputPath) { return { ok: false, error: 'missing_required_input', }; } try { const raw = fs.readFileSync(inputPath, 'utf8'); const parsed = JSON.parse(raw); return { ok: true, bytes: Buffer.byteLength(raw, 'utf8'), parsed, }; } catch (error) { return { ok: false, error: error instanceof Error ? error.message : String(error), }; } } function slugifySegment(value) { return String(value) .trim() .toLowerCase() .replace(/[^a-z0-9._-]+/g, '-') .replace(/^-+|-+$/g, '') .replace(/-{2,}/g, '-'); } function buildReceipt(payload) { const nextAction = payload?.nextDerivedAction ?? payload?.derivedAction ?? null; const receipt = { planId: payload?.planId ?? null, currentTask: payload?.currentTask ?? null, nextTaskId: payload?.nextTaskId ?? null, nextDerivedAction: nextAction, dispatchedAt: payload?.dispatchedAt ?? null, dispatchRunId: payload?.dispatchRunId ?? null, childSessionKey: payload?.childSessionKey ?? null, replyClosureState: payload?.replyClosureState ?? null, }; return receipt; } function validateReceipt(receipt) { const missing = []; for (const field of [ 'planId', 'currentTask', 'nextTaskId', 'nextDerivedAction', 'dispatchedAt', 'dispatchRunId', 'childSessionKey', 'replyClosureState', ]) { if (receipt[field] == null) { missing.push(field); } } const planIdSafe = slugifySegment(receipt.planId ?? ''); const dispatchRunIdSafe = slugifySegment(receipt.dispatchRunId ?? ''); if (!planIdSafe) missing.push('planId_filesystem_safe'); if (!dispatchRunIdSafe) missing.push('dispatchRunId_filesystem_safe'); return { ok: missing.length === 0, missing, planIdSafe, dispatchRunIdSafe, }; } function writeReceipt({ receipt, receiptDir, planIdSafe, dispatchRunIdSafe }) { fs.mkdirSync(receiptDir, { recursive: true }); const receiptPath = path.join(receiptDir, `receipt-${planIdSafe}-${dispatchRunIdSafe}.json`); fs.writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`, 'utf8'); return receiptPath; } const { inputPath, compact, receiptDir } = parseArgs(process.argv.slice(2)); const input = readInput(inputPath); let response; if (!input.ok) { response = { ok: false, status: 'input_error', binding: 'approved_plan_dispatch', compact, inputPath, receipt: null, receiptPath: null, input: { ok: false, error: input.error, }, }; } else { const receipt = buildReceipt(input.parsed); const validation = validateReceipt(receipt); if (!validation.ok) { response = { ok: false, status: 'missing_required_receipt_fields', binding: 'approved_plan_dispatch', compact, inputPath, receipt, receiptPath: null, missing: validation.missing, input: { ok: true, bytes: input.bytes, }, }; } else { const receiptPath = writeReceipt({ receipt, receiptDir, planIdSafe: validation.planIdSafe, dispatchRunIdSafe: validation.dispatchRunIdSafe, }); response = { ok: true, status: 'receipt_written', binding: 'approved_plan_dispatch', compact, inputPath, receipt, receiptPath, input: { ok: true, bytes: input.bytes, }, }; } } process.stdout.write(`${JSON.stringify(response)}\n`);