#!/usr/bin/env node
'use strict';
const path = require('node:path');
const {
nowIso,
randomId,
orchestrationDir,
loadArtifacts,
initArtifacts,
validateArtifacts,
writeJsonAtomic,
appendEvent,
updateDecisionObject,
updateApprovalObject,
autoApproveOneClick,
updateCheckpointObject,
transitionRun,
} = require('../lib/orchestration-state.js');
const { createProgressLogger, parseProgressArgs } = require('../lib/progress-log.js');
const { normalizeAutomationMode } = require('../lib/orchestration-decisions.js');
let progress;
function usage() {
console.log(`Usage:
node scripts/orchestration-state.js init --project-dir
[--project-id ]
node scripts/orchestration-state.js validate --project-dir
node scripts/orchestration-state.js status --project-dir
node scripts/orchestration-state.js decide --project-dir --key --value [--source user|deterministic_inference|prior_artifact] [--rationale ]
node scripts/orchestration-state.js approve --project-dir --type mapping|execution --status pending|approved|rejected|provisional [--notes ] [--decided-by user|system|agent]
node scripts/orchestration-state.js auto-approve --project-dir --type mapping|execution [--notes ] [--artifact-refs ]
node scripts/orchestration-state.js checkpoint --project-dir --phase discovery|mapping|setup|codegen|execution --patch
node scripts/orchestration-state.js transition --project-dir --state --phase [--status ] [--needs-user-input true|false] [--needs-llm true|false] [--resume-from ]
`);
}
function parseArgs(argv) {
const args = { _: [] };
for (let i = 0; i < argv.length; i += 1) {
const token = argv[i];
if (!token.startsWith('--')) {
args._.push(token);
continue;
}
const key = token.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
args[key] = argv[i + 1];
i += 1;
}
return args;
}
function parseMaybeJson(value) {
if (value == null) {
return value;
}
try {
return JSON.parse(value);
} catch {
return value;
}
}
function parseBoolean(value, fallback) {
if (value == null) {
return fallback;
}
return String(value) === 'true';
}
async function saveArtifacts(projectDir, artifacts) {
const dir = orchestrationDir(projectDir);
await writeJsonAtomic(path.join(dir, 'run.json'), artifacts.run);
await writeJsonAtomic(path.join(dir, 'checkpoints.json'), artifacts.checkpoints);
await writeJsonAtomic(path.join(dir, 'decisions.json'), artifacts.decisions);
await writeJsonAtomic(path.join(dir, 'approvals.json'), artifacts.approvals);
if (artifacts.preflight) {
await writeJsonAtomic(path.join(dir, 'preflight.json'), artifacts.preflight);
}
}
async function main() {
const parsed = parseProgressArgs(process.argv.slice(2));
progress = createProgressLogger({
script: 'skills/wix-replatform/scripts/orchestration-state.js',
...parsed.progress,
});
progress.start('Orchestration state command started', { phase: 'orchestration' });
const args = parseArgs(parsed.args);
const command = args._[0];
if (!command || command === 'help') {
usage();
if (command) {
progress.complete('Orchestration state help shown', { phase: 'orchestration', step: 'help' });
} else {
progress.error('Missing orchestration state command', { phase: 'orchestration' });
}
process.exit(command ? 0 : 1);
}
if (!args.projectDir) {
throw new Error('--project-dir is required');
}
const projectDir = path.resolve(args.projectDir);
if (command === 'init') {
const artifacts = await initArtifacts(projectDir, { projectId: args.projectId || path.basename(projectDir) });
console.log(JSON.stringify({ ok: true, projectDir, state: artifacts.run.currentState }, null, 2));
progress.complete('Orchestration artifacts initialized', { phase: 'orchestration', step: 'init', artifact: projectDir });
return;
}
const artifacts = await loadArtifacts(projectDir);
if (!artifacts.run) {
throw new Error(`orchestration artifacts not initialized under ${projectDir}`);
}
if (command === 'validate') {
const result = validateArtifacts(artifacts);
console.log(JSON.stringify(result, null, 2));
if (result.ok) {
progress.complete('Orchestration artifacts validated', { phase: 'orchestration', step: 'validate', artifact: projectDir });
} else {
progress.error('Orchestration artifact validation failed', { phase: 'orchestration', step: 'validate', artifact: projectDir, count: result.errors.length, unit: 'errors' });
}
process.exit(result.ok ? 0 : 1);
}
if (command === 'status') {
const result = validateArtifacts(artifacts);
console.log(JSON.stringify({
ok: result.ok,
currentState: artifacts.run.currentState,
activePhase: artifacts.run.activePhase,
runStatus: artifacts.run.status,
approvals: artifacts.approvals,
checkpointStatus: {
discovery: artifacts.checkpoints.discovery.status,
mapping: artifacts.checkpoints.mapping.status,
setup: artifacts.checkpoints.setup.status,
codegen: artifacts.checkpoints.codegen.status,
execution: artifacts.checkpoints.execution.status,
},
errors: result.errors,
}, null, 2));
if (result.ok) {
progress.complete('Orchestration status read', { phase: 'orchestration', step: 'status', artifact: projectDir });
} else {
progress.error('Orchestration status is invalid', { phase: 'orchestration', step: 'status', artifact: projectDir, count: result.errors.length, unit: 'errors' });
}
process.exit(result.ok ? 0 : 1);
}
if (command === 'decide') {
if (!args.key || args.value == null) {
throw new Error('decide requires --key and --value');
}
const decisionValue = parseMaybeJson(args.value);
if (['automationMode', 'interactionMode', 'runMode'].includes(args.key) &&
normalizeAutomationMode(decisionValue) === 'one_click' && !args.source) {
throw new Error('explicit one-click requires --source user');
}
updateDecisionObject(artifacts.decisions, args.key, decisionValue, {
source: args.source || 'user',
rationale: args.rationale || null,
timestamp: nowIso(),
});
await saveArtifacts(projectDir, artifacts);
await appendEvent(projectDir, {
id: randomId('evt'),
timestamp: nowIso(),
phase: artifacts.run.activePhase,
state: artifacts.run.currentState,
type: 'decision.recorded',
payload: { key: args.key },
});
console.log(JSON.stringify({ ok: true, decision: args.key }, null, 2));
progress.complete('Orchestration decision recorded', { phase: 'orchestration', step: 'decide', entity: args.key, artifact: projectDir });
return;
}
if (command === 'approve') {
if (!args.type || !args.status) {
throw new Error('approve requires --type and --status');
}
updateApprovalObject(artifacts.approvals, args.type, args.status, {
notes: args.notes || null,
decidedBy: args.decidedBy || 'user',
timestamp: nowIso(),
});
await saveArtifacts(projectDir, artifacts);
await appendEvent(projectDir, {
id: randomId('evt'),
timestamp: nowIso(),
phase: artifacts.run.activePhase,
state: artifacts.run.currentState,
type: 'approval.updated',
payload: { approvalType: args.type, status: args.status },
});
console.log(JSON.stringify({ ok: true, approvalType: args.type, status: args.status }, null, 2));
progress.complete('Orchestration approval updated', { phase: 'orchestration', step: 'approve', entity: args.type, artifact: projectDir });
return;
}
if (command === 'auto-approve' || command === 'autoApprove') {
if (!args.type) {
throw new Error('auto-approve requires --type');
}
const artifactRefs = args.artifactRefs == null ? [] : parseMaybeJson(args.artifactRefs);
if (!Array.isArray(artifactRefs)) {
throw new Error('--artifact-refs must be a JSON array when provided');
}
const result = autoApproveOneClick(artifacts, args.type, {
artifactRefs,
notes: args.notes || null,
timestamp: nowIso(),
});
if (!result.ok) {
throw new Error(`auto-approve failed: ${result.reason}`);
}
await saveArtifacts(projectDir, artifacts);
await appendEvent(projectDir, {
id: randomId('evt'),
timestamp: nowIso(),
phase: artifacts.run.activePhase,
state: artifacts.run.currentState,
type: 'approval.auto_approved',
payload: { approvalType: args.type },
});
console.log(JSON.stringify({ ok: true, approvalType: args.type, changed: result.changed }, null, 2));
progress.complete('Orchestration approval auto-approved', { phase: 'orchestration', step: 'auto-approve', entity: args.type, artifact: projectDir });
return;
}
if (command === 'checkpoint') {
if (!args.phase || !args.patch) {
throw new Error('checkpoint requires --phase and --patch');
}
updateCheckpointObject(artifacts.checkpoints, args.phase, parseMaybeJson(args.patch), nowIso());
await saveArtifacts(projectDir, artifacts);
console.log(JSON.stringify({ ok: true, phase: args.phase }, null, 2));
progress.complete('Orchestration checkpoint updated', { phase: 'orchestration', step: 'checkpoint', entity: args.phase, artifact: projectDir });
return;
}
if (command === 'transition') {
if (!args.state || !args.phase) {
throw new Error('transition requires --state and --phase');
}
artifacts.run = transitionRun(artifacts.run, {
state: args.state,
activePhase: args.phase,
status: args.status || artifacts.run.status,
needsUserInput: parseBoolean(args.needsUserInput, artifacts.run.needsUserInput),
needsLlm: parseBoolean(args.needsLlm, artifacts.run.needsLlm),
resumeFrom: args.resumeFrom ? parseMaybeJson(args.resumeFrom) : artifacts.run.resumeFrom,
timestamp: nowIso(),
});
await saveArtifacts(projectDir, artifacts);
await appendEvent(projectDir, {
id: randomId('evt'),
timestamp: nowIso(),
phase: artifacts.run.activePhase,
state: artifacts.run.currentState,
type: 'state.transitioned',
payload: { activePhase: artifacts.run.activePhase, runStatus: artifacts.run.status },
});
console.log(JSON.stringify({ ok: true, state: artifacts.run.currentState, phase: artifacts.run.activePhase }, null, 2));
progress.complete('Orchestration state transitioned', { phase: 'orchestration', step: 'transition', entity: args.state, artifact: projectDir });
return;
}
throw new Error(`unknown command: ${command}`);
}
main().catch((error) => {
if (progress) {
progress.error(error && error.message ? error.message : 'Orchestration state command failed', { phase: 'orchestration' });
}
console.error(error && error.message ? error.message : error);
process.exit(1);
});