Format authored monorepo code with pinned language formatters

This commit is contained in:
Timothy J. Aveni
2026-09-15 15:23:24 -07:00
parent a174faea5c
commit 00fc2b9ff1
69 changed files with 5135 additions and 3306 deletions
+297 -109
View File
@@ -2,21 +2,21 @@
import { readFile, writeFile, mkdtemp, rm, realpath } from "node:fs/promises";
import { parseQx, formatQx, lintQx } from "./source.js";
import { scaffoldAtom } from "./scaffold.js";
import {loadQxSources} from "./source-loader.js";
import { loadQxSources } 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 { 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 { 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 { 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";
@@ -25,18 +25,30 @@ 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"});
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)};
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); }
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<ReturnType<typeof planStructure>>) => ({
root: plan.root, validation: plan.validation,
changes: plan.changes.map(change => ({file: change.file, beforeBytes: change.before?.length ?? 0, afterBytes: change.after?.length ?? 0})),
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.",
});
@@ -47,43 +59,62 @@ const main = async () => {
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");
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"));
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 { source } = await loadQxSources(args[0]);
const syntax = parseQx(source);
const text = (n: {start: number; end: number}) => source.slice(n.start, n.end);
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")!);
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");
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");
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") {
@@ -108,29 +139,50 @@ const main = async () => {
}
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} = {};
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"});
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)");
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"});
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;
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]);
@@ -140,18 +192,24 @@ const main = async () => {
}
if (command === "check-committed" && !args.includes("--help")) {
const [kind, repository, commit, log, ...extra] = args;
if (!["workspace", "interface", "package"].includes(kind) || !log || extra.length) throw new Error("usage: quixos-qx check-committed workspace|interface|package REPOSITORY COMMIT LOG_FILE");
process.stdout.write(`${await buildImmutableCandidate({repository, commit}, kind as "workspace" | "interface" | "package", log)}\n`);
if (!["workspace", "interface", "package"].includes(kind) || !log || extra.length)
throw new Error("usage: quixos-qx check-committed workspace|interface|package REPOSITORY COMMIT LOG_FILE");
process.stdout.write(
`${await buildImmutableCandidate({ repository, commit }, kind as "workspace" | "interface" | "package", log)}\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-function, 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");
process.stdout.write(
"quixos-qx: author-check, author-contract, converge, worklist, inspect, resources, check-committed, source-baseline, scaffold-package, scaffold-interface, scaffold-function, 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]" : ""}`);
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;
@@ -165,27 +223,56 @@ const main = async () => {
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"].includes(kind) && name) {
if (flags.length && !flags[0].startsWith("--")) {
repository = flags.shift();
commit = flags.shift();
} else if (root && ["interface", "package"].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 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}`);
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;
repository = source.repository;
commit = source.commit;
}
if (!root || !["interface", "package"].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 NAME REPOSITORY COMMIT [--write]");
if (
!root ||
!["interface", "package"].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 NAME REPOSITORY COMMIT [--write]");
const resourceKind = kind as "package" | "interface";
let entrypoint: "workspace" | "package" | "interface" | 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;}
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) throw new Error("No QX repository entrypoint");
const lock = await loadQuixosLock(path.join(root, "quixos.lock"));
@@ -193,45 +280,84 @@ const main = async () => {
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;
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: [
{file: `${entrypoint}.qx`, edits: [{operation: "import", kind: resourceKind, name}]},
{file: target, edits: [{operation: "dependency", kind: resourceKind, name, source: {repository, commit}}]},
]};
const request: StructuralRequest = {
kind: entrypoint,
source: await authorSource(root),
validation: "syntax",
files: [
{ file: `${entrypoint}.qx`, edits: [{ operation: "import", 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`);
process.stdout.write(
`${JSON.stringify({ ...planSummary(plan), applied: flags.includes("--write") ? await applyStructure(plan) : undefined }, null, 2)}\n`,
);
return;
}
if (command === "scaffold-interface") {
const [root, specFile, ...flags] = args;
if (!root || !specFile || flags.some(flag => flag !== "--write")) throw new Error("usage: quixos-qx scaffold-interface ROOT SPEC_JSON [--write]");
const spec = await readSpec(specFile) as ScaffoldRecipe;
if (!spec.name || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.name) || !spec.id || !spec.revision || !spec.tools?.quixos) throw new Error("Interface scaffold requires name, id, revision and Quixos toolchain source");
const request: StructuralRequest = {kind: "interface", source: spec.source, files: [
{file: "interface.qx", create: spec.declaration ?? `interface ${spec.name} id ${JSON.stringify(spec.id)} revision ${JSON.stringify(spec.revision)} {\n}\n`},
{file: "quixos.lock", create: formatQuixosLock({formatVersion: 1, quixos: {resolver: "git", ...spec.tools.quixos}, resources: []})},
{file: ".gitignore", create: ".quixos/\n"},
]};
if (!root || !specFile || flags.some((flag) => flag !== "--write"))
throw new Error("usage: quixos-qx scaffold-interface ROOT SPEC_JSON [--write]");
const spec = (await readSpec(specFile)) as ScaffoldRecipe;
if (!spec.name || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.name) || !spec.id || !spec.revision || !spec.tools?.quixos)
throw new Error("Interface scaffold requires name, id, revision and Quixos toolchain source");
const request: StructuralRequest = {
kind: "interface",
source: spec.source,
files: [
{
file: "interface.qx",
create:
spec.declaration ??
`interface ${spec.name} id ${JSON.stringify(spec.id)} revision ${JSON.stringify(spec.revision)} {\n}\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`);
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");
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});}
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;
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;
@@ -239,66 +365,112 @@ const main = async () => {
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`);
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. Dependency installation uses scaffold install.");
if (command === "scaffold-refresh")
throw new Error(
"Refresh is no longer required: edit declarations and typed implementation wiring, then run qx-workspace check. Dependency installation uses scaffold install.",
);
if (["scaffold-package", "scaffold-function", "scaffold-migration", "scaffold-install"].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");
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}`};
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 (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|migration|refresh ROOT SPEC_JSON [--write [--install]]");
const plan = command === "scaffold-install" ? undefined : await planStructure(root,
await scaffoldRecipe(root, command.slice(9) as "package" | "function" | "migration", spec), process.env.QUIXOS_SNAPSHOT_MAP);
} 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|migration|refresh ROOT SPEC_JSON [--write [--install]]",
);
const plan =
command === "scaffold-install"
? undefined
: await planStructure(
root,
await scaffoldRecipe(root, command.slice(9) as "package" | "function" | "migration", spec),
process.env.QUIXOS_SNAPSHOT_MAP,
);
const applied = plan && flags.includes("--write") ? await applyStructure(plan) : undefined;
if (flags.includes("--install")) {
const cwd = path.resolve(root, spec.directory ?? "");
const toolchain = JSON.parse(await readFile(path.join(cwd, "quixos.toolchain.json"), "utf8"));
if (toolchain.generatedBy !== "qx-scaffold-v1" || typeof toolchain.nixifyPluginUrl !== "string") throw new Error("Missing scaffold toolchain");
for (const [executable, args] of [["corepack", ["yarn", "plugin", "import", toolchain.nixifyPluginUrl]], ["corepack", ["yarn", "config", "set", "generateDefaultNix", "false"]], ["corepack", ["yarn", "config", "set", "individualNixPackaging", "true"]], ["corepack", ["yarn", "install"]]] as const) {
const result = spawnSync(executable, [...args], {cwd, stdio: ["inherit", 2, 2]});
if (result.error || result.status !== 0) throw new Error(`Scaffold files retained; ${executable} ${args.join(" ")} failed: ${result.error?.message ?? result.status}`);
if (toolchain.generatedBy !== "qx-scaffold-v1" || typeof toolchain.nixifyPluginUrl !== "string")
throw new Error("Missing scaffold toolchain");
for (const [executable, args] of [
["corepack", ["yarn", "plugin", "import", toolchain.nixifyPluginUrl]],
["corepack", ["yarn", "config", "set", "generateDefaultNix", "false"]],
["corepack", ["yarn", "config", "set", "individualNixPackaging", "true"]],
["corepack", ["yarn", "install"]],
] as const) {
const result = spawnSync(executable, [...args], { cwd, stdio: ["inherit", 2, 2] });
if (result.error || result.status !== 0)
throw new Error(
`Scaffold files retained; ${executable} ${args.join(" ")} failed: ${result.error?.message ?? result.status}`,
);
}
try {
await readFile(path.join(cwd, "yarn-project.nix"));
} catch {
throw new Error(
"Nixify did not generate yarn-project.nix. It skips repositories under the OS temporary directory; use an ordinary workspace checkout and retry installation.",
);
}
try {await readFile(path.join(cwd, "yarn-project.nix"));}
catch {throw new Error("Nixify did not generate yarn-project.nix. It skips repositories under the OS temporary directory; use an ordinary workspace checkout and retry installation.");}
await snapshotCommit(cwd);
const locked = spawnSync("nix", ["flake", "lock"], {cwd, stdio: ["inherit", 2, 2]});
const locked = spawnSync("nix", ["flake", "lock"], { cwd, stdio: ["inherit", 2, 2] });
if (locked.error || locked.status !== 0) throw new Error("Scaffold files retained; nix flake lock failed");
}
process.stdout.write(`${JSON.stringify({...plan ? planSummary(plan) : {installed: true}, applied}, null, 2)}\n`);
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 (!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`);
process.stdout.write(`${JSON.stringify({ ...planSummary(plan), applied }, null, 2)}\n`);
return;
}
if (command === "scaffold-resume") {
@@ -307,10 +479,14 @@ const main = async () => {
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 (["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]");
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")) : [];
@@ -320,23 +496,35 @@ const main = async () => {
}
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]");
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)]");
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);
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;
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; });
main().catch((error: unknown) => {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
});