#!/usr/bin/env node import { readFile, writeFile, mkdtemp, rm, realpath } from "node:fs/promises"; import { parseQx, formatQx, lintQx } from "./source.js"; import { scaffoldAtom } from "./scaffold.js"; import { loadQxSources, qxSourceManifest } from "./source-loader.js"; import { createGitCapabilityResolver } from "./git-resolver.js"; import { planEvolution } from "../capability-model/index.js"; import { snapshotRepository } from "./candidate-check.js"; import { planPinUpgrades, applyPinUpgrades, discoverUpgradeSpec, type UpgradeSpec } from "./pin-upgrades.js"; import path from "node:path"; import os from "node:os"; import { spawnSync } from "node:child_process"; import { planStructure, applyStructure, resumeStructure, type StructuralRequest } from "./structural-plan.js"; import { scaffoldRecipe, type ScaffoldRecipe } from "./scaffold-recipes.js"; import { checkBundleSources } from "../bindings/bundle-policy.js"; import { sealMigrations } from "./migration-seal.js"; import { generatePackageDescriptor } from "../bindings/index.js"; import { buildCheckedPackage, buildImmutableCandidate, snapshotCommit } from "./checked-build.js"; import { formatQuixosLock, loadQuixosLock, parseQuixosLockDocument } from "../resource-lock/index.js"; import { walkSyntax } from "./source.js"; import { inspectWorkbench } from "./authoring-inspect.js"; import { authoringContext } from "./authoring-context.js"; import { convergeAuthoring } from "./authoring-converge.js"; import { checkAuthoring } from "./authoring-check.js"; import { authoringWorklist } from "./authoring-worklist.js"; const authorSource = async (root: string) => { const result = spawnSync("git", ["config", "--get", "remote.origin.url"], { cwd: root, encoding: "utf8" }); if (result.error || result.status !== 0) throw new Error("Managed resource has no origin"); return { repository: result.stdout.trim(), commit: await snapshotCommit(root) }; }; const readSpec = async (value: string) => { if (value === "-") { let input = ""; for await (const chunk of process.stdin) { input += chunk; if (input.length > 1024 * 1024) throw new Error("Scaffold specification exceeds 1 MiB"); } return JSON.parse(input); } return JSON.parse(value.trimStart().startsWith("{") ? value : await readFile(value, "utf8")); }; const planSummary = (plan: Awaited>) => ({ root: plan.root, validation: plan.validation, changes: plan.changes.map((change) => ({ file: change.file, beforeBytes: change.before?.length ?? 0, afterBytes: change.after?.length ?? 0, })), note: "Structural plan only, not implementation verification. Run qx-workspace check while iterating.", }); const main = async () => { const [command, ...args] = process.argv.slice(2); if (command === "source-locks") { if (!args[0] || (args.length !== 1 && (args.length !== 3 || args[1] !== "--replacements"))) throw Error("source-locks DIRECTORY [--replacements JSON_FILE]"); const { workspaceSourceLocks } = await import("./source-locks.js"); const replacements = args[2] ? JSON.parse(await readFile(args[2], "utf8")) : []; process.stdout.write(JSON.stringify(await workspaceSourceLocks(path.resolve(args[0]), replacements)) + "\n"); return; } if (["source-checkout", "source-develop", "source-backup", "source-restore"].includes(command!)) { const args = ["source", command.slice("source-".length), ...process.argv.slice(3)]; const { materializeWorkspaceSources } = await import("./source-closure.js"); if (!args[2]) throw Error("source requires a pinned source JSON file"); const flags = new Map(); for (let i = 3; i < args.length; i += 2) { if ( ![ "--dir", ...(args[1] !== "restore" ? ["--locations"] : []), ...(args[1] === "checkout" ? ["--repository-prefix"] : []), ...(args[1] === "develop" ? ["--cache", "--workspace-id"] : []), ...(args[1] === "backup" ? ["--output"] : []), ].includes(args[i]) || !args[i + 1] || flags.has(args[i]) ) throw Error("Invalid source command options; use qx-workspace help source"); flags.set(args[i], args[i + 1]); } if (!flags.get("--dir")) throw Error("source requires --dir DIRECTORY"); if (args[1] === "restore") { const { restoreWorkspaceSources } = await import("./source-backup.js"); const result = await restoreWorkspaceSources({ backup: args[2], directory: path.resolve(flags.get("--dir")!) }); process.stdout.write(JSON.stringify(result, null, 2) + "\n"); return; } const locationsFile = flags.get("--locations"); const options = { source: JSON.parse(await readFile(args[2], "utf8")), directory: path.resolve(flags.get("--dir")!), ...(locationsFile ? { locations: JSON.parse(await readFile(locationsFile, "utf8")) } : {}), ...(flags.has("--repository-prefix") ? { repositoryPrefix: flags.get("--repository-prefix") } : {}), }; let result; if (args[1] === "develop") { if (!flags.get("--cache")) throw Error("source develop requires --cache DIRECTORY"); const { prepareDevelopmentSources } = await import("./development-sources.js"); result = await prepareDevelopmentSources({ ...options, cacheDirectory: path.resolve(flags.get("--cache")!), ...(flags.has("--workspace-id") ? { workspaceId: flags.get("--workspace-id")! } : {}), }); } else if (args[1] === "backup") { if (!flags.get("--output")) throw Error("source backup requires --output DIRECTORY"); const { backupWorkspaceSources } = await import("./source-backup.js"); result = await backupWorkspaceSources({ ...options, output: path.resolve(flags.get("--output")!) }); } else result = await materializeWorkspaceSources(options); process.stdout.write(JSON.stringify(result, null, 2) + "\n"); return; } if (command === "source-manifest") { if (args.length !== 1) throw new Error("source-manifest WORKSPACE_ROOT"); process.stdout.write(JSON.stringify(await qxSourceManifest(args[0])) + "\n"); return; } if (command === "scaffold-validate-qx") { if (args.length !== 1) throw new Error("scaffold-validate-qx SOURCES_JSON"); const sources = JSON.parse(args[0]); if (!Array.isArray(sources) || sources.length > 100) throw new Error("Expected at most 100 scaffold sources"); for (const entry of sources) { if ( !entry || typeof entry.file !== "string" || typeof entry.source !== "string" || entry.source.length > 1024 * 1024 ) throw new Error("Invalid scaffold source"); const diagnostics = parseQx(entry.source, entry.file).diagnostics; if (diagnostics.length) throw new Error(diagnostics.map((d) => `${entry.file}:${d.line}:${d.column + 1}: ${d.message}`).join("\n")); } process.stdout.write('{"syntaxValid":true,"verificationEvidence":false}\n'); return; } if (command === "scaffold-placement-binding") { if (args.length !== 1) throw new Error("scaffold-placement-binding ROOT"); const { source } = await loadQxSources(args[0]); const syntax = parseQx(source); const text = (n: { start: number; end: number }) => source.slice(n.start, n.end); const matches: string[] = []; for (const edge of walkSyntax(syntax.root)) { if (edge.kind !== "edgeDecl") continue; for (const endpoint of edge.children.filter((n) => n.kind === "edgeEndpoint")) { const target = endpoint.children.find((n) => n.kind === "targetConstraint"); if ( !target || !syntax.tokens.some((t) => t.start >= target.start && t.end <= target.end && t.kind === "INTERFACE") || !target.children.some((n) => n.kind === "identifier" && text(n) === "WebStudioPlaceable") ) continue; const edgeName = text(edge.children.find((n) => n.kind === "identifier")!); const projection = text(endpoint.children.find((n) => n.kind === "identifier")!); matches.push(`bind placements.resolve to edge ${edgeName}.${projection}.resolve;`); } } if (matches.length !== 1) throw new Error( `Expected one canvas placement edge for WebStudioPlaceable; found ${matches.length}. Configure the workspace canvas before adding a bundle.`, ); process.stdout.write(JSON.stringify({ binding: matches[0] }) + "\n"); return; } if (command === "package-identity") { if (args.length !== 1) throw new Error("usage: quixos-qx package-identity PACKAGE_QX"); const authored = await readFile(args[0], "utf8"); const syntax = parseQx(authored); const declarations = [...walkSyntax(syntax.root)].filter((node) => node.kind === "packageResourceDecl"); if (syntax.diagnostics.length || declarations.length !== 1) throw new Error("Expected one valid package declaration"); const literals = declarations[0].children.filter((node) => node.kind === "stringLiteral"); process.stdout.write( JSON.stringify({ id: JSON.parse(authored.slice(literals[0].start, literals[0].end)), revision: JSON.parse(authored.slice(literals[1].start, literals[1].end)), }) + "\n", ); return; } if (command === "package-descriptor") { if (args.length !== 2) throw new Error("usage: quixos-qx package-descriptor SCHEMA REVISION_ID"); process.stdout.write(generatePackageDescriptor(JSON.parse(await readFile(args[0], "utf8")), args[1])); return; } if (command === "migration-seal") { if (args.length !== 1) throw new Error("usage: quixos-qx migration-seal PACKAGE_DIRECTORY"); process.stdout.write(JSON.stringify(await sealMigrations(args[0])) + "\n"); return; } if (command === "bundle-policy") { if (args.length !== 1) throw new Error("usage: quixos-qx bundle-policy SOURCE_DIRECTORY"); await checkBundleSources(args[0]); return; } if (command === "worklist" && !args.includes("--help")) { if (args.length !== 1) throw new Error("usage: quixos-qx worklist WORKBENCH"); process.stdout.write(`${JSON.stringify(await authoringWorklist(args[0]), null, 2)}\n`); return; } if (["author-check", "author-contract"].includes(command) && !args.includes("--help")) { const [root, output, ...flags] = args; if (!root || !output) throw new Error("usage: quixos-qx author-check ROOT OUTPUT [--baseline FILE] [--reviews FILE]"); const options: { baseline?: string; reviews?: string } = {}; for (let index = 0; index < flags.length; index += 2) { if (!flags[index + 1]) throw new Error("Missing check option value"); if (flags[index] === "--baseline") options.baseline = flags[index + 1]; else if (flags[index] === "--reviews") options.reviews = flags[index + 1]; else throw new Error(`Unknown check option ${flags[index]}`); } const result = await checkAuthoring(root, output, { ...options, contractOnly: command === "author-contract" }); process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); if (result.blockers.length) process.exitCode = 1; return; } if (["converge", "_converge"].includes(command) && !args.includes("--help")) { if (!args.length || args.length > 2) throw new Error("usage: quixos-qx converge WORKBENCH [REGISTERED_DIRECTORY] (join package writers first)"); const context = await authoringContext(args[0]); if (command === "converge") { console.error(`[${new Date().toISOString()}] Capture: waiting for coordinator lock (120s limit)`); const result = spawnSync( "flock", [ "--exclusive", "--timeout", "120", "--conflict-exit-code", "75", path.join(context.workbench, ".quixos/converge.lock"), process.execPath, process.argv[1], "_converge", context.workbench, ...(args[1] ? [args[1]] : []), ], { stdio: "inherit" }, ); if (result.error) throw result.error; if (result.status === 75) process.stderr.write( "Timed out after 120 seconds waiting for source capture; inspect the active coordinator. No build lock is held.\n", ); process.exitCode = result.status ?? 1; return; } console.error(`[${new Date().toISOString()}] Capture: coordinator lock acquired`); const result = await convergeAuthoring(context.workbench, args[1]); process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); if (!result.converged) process.exitCode = 1; return; } if (command === "check-committed" && !args.includes("--help")) { const [kind, repository, commit, log, ...extra] = args; if ( !["workspace", "interface", "package", "build"].includes(kind) || !log || (extra.length !== 0 && (kind !== "workspace" || extra.length !== 2 || extra[0] !== "--workspace-id" || !extra[1])) ) throw new Error( "usage: quixos-qx check-committed workspace|interface|package|build REPOSITORY COMMIT LOG_FILE [--workspace-id ID]", ); process.stdout.write( `${await buildImmutableCandidate({ repository, commit }, kind as "workspace" | "interface" | "package" | "build", log, false, undefined, extra[1])}\n`, ); return; } if (!command || command === "--help" || args.includes("--help")) { process.stdout.write( "quixos-qx: author-check, author-contract, converge, worklist, inspect, resources, check-committed, source-baseline, scaffold-package, scaffold-interface, scaffold-build, scaffold-function, scaffold-query, scaffold-dependency, scaffold-structure, scaffold-resume, pin-upgrade, parse, lint, format\n" + "inspect WORKBENCH [RESOURCE] shows provisional contracts, with explicit historical fallback; never verification evidence.\n" + "resources WORKBENCH lists registered editable repositories. Use qx-workspace for the workspace authoring workflow.\n", ); return; } if (command === "inspect" || command === "resources") { if (!args[0] || args.length > (command === "inspect" ? 2 : 1)) throw new Error(`usage: quixos-qx ${command} WORKBENCH${command === "inspect" ? " [RESOURCE]" : ""}`); const result = command === "inspect" ? await inspectWorkbench(args[0], args[1]) : await authoringContext(args[0]); process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); return; } if (command === "source-baseline") { if (args.length !== 1) throw new Error("usage: quixos-qx source-baseline WORKBENCH"); process.stdout.write(`${JSON.stringify(await (await authoringContext(args[0])).baseline())}\n`); return; } if (command === "scaffold-dependency") { const [root, kind, name, ...remaining] = args; let repository: string | undefined, commit: string | undefined; const flags = [...remaining]; if (flags.length && !flags[0].startsWith("--")) { repository = flags.shift(); commit = flags.shift(); } else if (root && ["interface", "package", "build"].includes(kind) && name) { const context = await authoringContext(root); const alias = await realpath(path.join(context.workbench, `${kind}s`, name)).catch(() => null); const matches = context.resources.filter( (entry) => entry.kind === kind && (entry.resourceId === name || path.basename(entry.directory) === name || path.join(context.workbench, entry.directory) === alias), ); if (matches.length !== 1 || !matches[0].source) throw new Error(`Select exactly one registered ${kind} with qx-workspace resources; no match for ${name}`); const selected = matches[0]; let source = selected.source!; if (flags.includes("--write")) { const retained = spawnSync("quixos-qx", ["converge", context.workbench, selected.directory], { encoding: "utf8", env: { ...process.env, QUIXOS_JJ_NO_CHECKPOINT: "1" }, }); if (retained.error || retained.status !== 0) throw new Error( `Dependency source needs attention: ${retained.error?.message ?? retained.stdout ?? retained.stderr}`, ); source = JSON.parse(retained.stdout).candidate; } repository = source.repository; commit = source.commit; } if ( !root || !["interface", "package", "build"].includes(kind) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name ?? "") || !repository || !commit || flags.some((flag) => flag !== "--write") ) throw new Error( "usage: quixos-qx scaffold-dependency ROOT interface|package|build NAME REPOSITORY COMMIT [--write]", ); const resourceKind = kind as "package" | "interface" | "build"; let entrypoint: "workspace" | "package" | "interface" | "build" | undefined; for (const candidate of ["workspace", "package", "interface"] as const) { try { await readFile(path.join(root, `${candidate}.qx`)); if (entrypoint) throw new Error("Ambiguous repository entrypoint"); entrypoint = candidate; } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } } if (!entrypoint) { const context = await authoringContext(root); const selected = context.resources.find( (entry) => path.join(context.workbench, entry.directory) === path.resolve(root), ); if (selected?.kind !== "build") throw new Error("No QX repository entrypoint"); entrypoint = "build"; } const lock = await loadQuixosLock(path.join(root, "quixos.lock")); if (!lock.ok) throw new Error("Invalid resource lock"); let target = "quixos.lock"; for (const file of lock.lock.sourceFiles ?? ["quixos.lock"]) { const parsed = parseQuixosLockDocument(await readFile(path.join(root, file), "utf8")); if (parsed.ok && parsed.document.resources.some((entry) => entry.kind === kind && entry.binding === name)) target = file; } const request: StructuralRequest = { kind: entrypoint, source: await authorSource(root), validation: "syntax", files: [ ...(entrypoint !== "build" && resourceKind !== "build" ? [{ file: `${entrypoint}.qx`, edits: [{ operation: "import" as const, kind: resourceKind, name }] }] : []), { file: target, edits: [{ operation: "dependency", kind: resourceKind, name, source: { repository, commit } }], }, ], }; const plan = await planStructure(root, request, process.env.QUIXOS_SNAPSHOT_MAP); process.stdout.write( `${JSON.stringify({ ...planSummary(plan), applied: flags.includes("--write") ? await applyStructure(plan) : undefined }, null, 2)}\n`, ); return; } if (command === "scaffold-interface" || command === "scaffold-build") { const isBuild = command === "scaffold-build"; const [root, specFile, ...flags] = args; if (!root || !specFile || flags.some((flag) => flag !== "--write")) throw new Error("usage: quixos-qx scaffold-interface|scaffold-build ROOT SPEC_JSON [--write]"); const spec = (await readSpec(specFile)) as ScaffoldRecipe; if ( !spec.name || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.name) || (!isBuild && (!spec.id || !spec.revision)) || !spec.tools?.quixos ) throw new Error( isBuild ? "Build scaffold requires name and Quixos toolchain source" : "Interface scaffold requires name, id, revision and Quixos toolchain source", ); const request: StructuralRequest = { kind: isBuild ? "build" : "interface", // Like package scaffolding, declaration creation is provisional. Imports // can be attached next; the normal check verifies the complete graph. validation: "syntax", source: spec.source, files: [ ...(!isBuild ? [ { file: "interface.qx", create: spec.declaration ?? `interface ${spec.name} id ${JSON.stringify(spec.id)} revision ${JSON.stringify(spec.revision)} {\n}\n`, }, ] : [ { file: "README.md", create: `# ${spec.name}\n\nManaged build dependency. Consumers select sources or Nix outputs through context.dependencies.\n`, }, ]), { file: "quixos.lock", create: formatQuixosLock({ formatVersion: 1, quixos: { resolver: "git", ...spec.tools.quixos }, resources: [], }), }, { file: ".gitignore", create: ".quixos/\n" }, ], }; const plan = await planStructure(root, request, process.env.QUIXOS_SNAPSHOT_MAP); process.stdout.write( `${JSON.stringify({ ...planSummary(plan), applied: flags.includes("--write") ? await applyStructure(plan) : undefined }, null, 2)}\n`, ); return; } if (command === "build-package") { if (args.length !== 3) throw new Error("usage: quixos-qx build-package COMMITTED_SOURCE SCHEMA PACKAGE_REVISION_ID"); process.stdout.write(`${await buildCheckedPackage(args[0], args[1], args[2])}\n`); return; } if (command === "source-digest") { if (!args[0] || args.length !== 1) throw new Error("usage: quixos-qx source-digest ROOT"); const temporary = await mkdtemp(path.join(os.tmpdir(), "qx-source-digest-")); try { process.stdout.write(`${(await snapshotRepository(args[0], temporary)).treeDigest}\n`); } finally { await rm(temporary, { recursive: true, force: true }); } return; } if (command === "pin-upgrade") { const [workbench, specFile, ...flags] = args; if (!workbench || !specFile) throw new Error("usage: quixos-qx pin-upgrade WORKBENCH SPEC_JSON [--publish] [--resume UUID]"); let publish = false, acceptEdits = false, resume: string | undefined; for (let index = 0; index < flags.length; index++) { if (flags[index] === "--publish") publish = true; else if (flags[index] === "--accept-edits") acceptEdits = true; else if (flags[index] === "--resume" && /^[a-f0-9-]{36}$/.test(flags[index + 1] ?? "")) resume = flags[++index]; else throw new Error(`Unknown pin-upgrade option ${flags[index]}`); } if (acceptEdits && !resume) throw new Error("--accept-edits requires an existing refactor journal (--resume)"); const plan = resume ? JSON.parse(await readFile(path.join(workbench, ".quixos/upgrades", `${resume}.json`), "utf8")).plan : await planPinUpgrades( workbench, specFile === "auto" ? await discoverUpgradeSpec(workbench) : (JSON.parse(await readFile(specFile, "utf8")) as UpgradeSpec), ); if (resume && path.resolve(workbench) !== plan.workbench) throw new Error("Upgrade journal belongs to another workbench"); process.stdout.write( `${JSON.stringify(publish ? await applyPinUpgrades(plan, resume, undefined, { acceptEdits }) : plan, null, 2)}\n`, ); return; } if (command === "scaffold-refresh") throw new Error( "Refresh is no longer required: edit declarations and typed implementation wiring, then run qx-workspace check. Dependencies are provided by checkedArtifacts.", ); if (command === "scaffold-install") throw Error("Execution dependencies are supplied by checkedArtifacts; run workspace-root check instead"); if ( [ "scaffold-package", "scaffold-function", "scaffold-query", "scaffold-component", "scaffold-controller", "scaffold-migration", ].includes(command) ) { const [root, specFile, ...flags] = args; let spec: ScaffoldRecipe; if (command === "scaffold-function" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(specFile ?? "")) { const authored = parseQx(await readFile(path.join(root, "package.qx"), "utf8")); const declarationNode = [...walkSyntax(authored.root)].find((node) => node.kind === "packageResourceDecl"); const nameNode = declarationNode?.children.find((node) => node.kind === "identifier"); if (!nameNode) throw new Error("Expected a package declaration"); const name = authored.source.slice(nameNode.start, nameNode.end); spec = { source: await authorSource(root), name: specFile, id: `export:${name}:${specFile}` }; const declaration = flags.indexOf("--declaration"); if (declaration >= 0) { if (!flags[declaration + 1]) throw new Error("--declaration requires a QX declaration file"); spec.declaration = await readFile(flags[declaration + 1], "utf8"); const parsed = parseQx(`package Draft id "package:draft" revision "package:draft@1" { ${spec.declaration} }`); if (parsed.diagnostics.length) throw new Error(parsed.diagnostics.map((d) => d.message).join("\n")); const exported = [...walkSyntax(parsed.root)].filter((node) => ["packageOperationExport", "packageFunctionExport", "packageConstructorExport"].includes(node.kind), ); if (exported.length !== 1) throw new Error("--declaration must contain exactly one function, operation or constructor export"); const literal = exported[0].children.find((node) => node.kind === "stringLiteral"); if (!literal) throw new Error("Declaration requires an authored export ID"); spec.id = JSON.parse(parsed.source.slice(literal.start, literal.end)); flags.splice(declaration, 2); } } else spec = (await readSpec(specFile)) as ScaffoldRecipe; if ( !root || !specFile || flags.some((flag) => !["--write", "--install"].includes(flag)) || (flags.includes("--install") && !flags.includes("--write")) ) throw new Error("usage: quixos-qx scaffold-package|function|query|component|controller ROOT SPEC_JSON [--write]"); const plan = command === "scaffold-install" ? undefined : await planStructure( root, await scaffoldRecipe( root, command.slice(9) as "package" | "function" | "query" | "component" | "controller" | "migration", spec, ), process.env.QUIXOS_SNAPSHOT_MAP, ); if (flags.includes("--install")) throw Error( "Execution dependencies are supplied by checkedArtifacts; run workspace-root check instead of scaffold install", ); const applied = plan && flags.includes("--write") ? await applyStructure(plan) : undefined; process.stdout.write( `${JSON.stringify({ ...(plan ? planSummary(plan) : { installed: true }), applied }, null, 2)}\n`, ); return; } if (command === "scaffold-structure") { const [root, spec, ...flags] = args; if (!root || !spec || flags.some((flag) => flag !== "--write")) throw new Error("usage: quixos-qx scaffold-structure ROOT SPEC_JSON [--write]"); const request = (await readSpec(spec)) as StructuralRequest; if (request.kind !== "workspace" && !request.source) request.source = await authorSource(root); const plan = await planStructure(root, request, process.env.QUIXOS_SNAPSHOT_MAP); const applied = flags.includes("--write") ? await applyStructure(plan) : undefined; process.stdout.write(`${JSON.stringify({ ...planSummary(plan), applied }, null, 2)}\n`); return; } if (command === "scaffold-resume") { const [root, id, ...extra] = args; if (!root || !id || extra.length) throw new Error("usage: quixos-qx scaffold-resume ROOT JOURNAL_ID"); process.stdout.write(`${JSON.stringify(await resumeStructure(root, id), null, 2)}\n`); return; } if (["check", "check-resource"].includes(command)) throw new Error( "Use qx-workspace check in the registered repository; handwritten source/snapshot-map candidates are no longer an authoring check path", ); if (command === "evolution") { const [baseline, candidate, reviews, ...extra] = args; if (!baseline || !candidate || extra.length) throw new Error("usage: quixos-qx evolution BASELINE_JSON CANDIDATE_JSON [REVIEWS_JSON]"); const before = baseline === "none" ? null : JSON.parse(await readFile(baseline, "utf8")); const after = JSON.parse(await readFile(candidate, "utf8")); const decisions = reviews ? JSON.parse(await readFile(reviews, "utf8")) : []; if (!Array.isArray(decisions)) throw new Error("Reviews must be an array"); process.stdout.write(`${JSON.stringify(planEvolution(before, after, { reviews: decisions }), null, 2)}\n`); return; } if (command === "scaffold-atom") { const [root, name, id, ...flags] = args; if (!root || !name || !id || flags.some((flag) => flag !== "--write")) throw new Error("usage: quixos-qx scaffold-atom ROOT NAME ID [--write]"); const resolveResource = await createGitCapabilityResolver({ checkoutRoot: `${root}/.quixos/resource-checkouts` }); const plan = await scaffoldAtom({ root, name, id, write: flags.includes("--write"), resolveResource }); process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`); return; } const [file, flag, ...rest] = args; if ( !file || rest.length || (flag && flag !== "--write") || !["parse", "lint", "format"].includes(command ?? "") || (flag && command !== "format") ) throw new Error("usage: quixos-qx parse|lint|format FILE [--write (format only)]"); const source = await readFile(file, "utf8"); if (command === "format") { const formatted = formatQx(source); if (flag) await writeFile(file, formatted); else process.stdout.write(formatted); } else { const result = command === "parse" ? parseQx(source, file) : lintQx(source, file); process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); if (Array.isArray(result) ? result.some((entry) => entry.severity === "error") : result.diagnostics.length) process.exitCode = 1; } }; main().catch((error: unknown) => { console.error(error instanceof Error ? error.message : error); process.exitCode = 1; });