2026-09-08 14:27:24 -07:00
#!/usr/bin/env node
2026-09-14 10:26:11 -07:00
import { readFile , writeFile , mkdtemp , rm , realpath } from "node:fs/promises" ;
2026-09-08 14:27:24 -07:00
import { parseQx , formatQx , lintQx } from "./source.js" ;
import { scaffoldAtom } from "./scaffold.js" ;
2026-09-15 13:45:42 -07:00
import { loadQxSources } from "./source-loader.js" ;
2026-09-08 14:27:24 -07:00
import { createGitCapabilityResolver } from "./git-resolver.js" ;
2026-09-10 18:27:41 -07:00
import { planEvolution } from "../capability-model/index.js" ;
2026-09-14 10:26:11 -07:00
import { snapshotRepository } from "./candidate-check.js" ;
2026-09-10 18:27:41 -07:00
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" ;
2026-09-14 20:35:53 -07:00
import { checkBundleSources } from "../bindings/bundle-policy.js" ;
import { sealMigrations } from "./migration-seal.js" ;
import { generatePackageDescriptor } from "../bindings/index.js" ;
2026-09-14 10:26:11 -07:00
import { buildCheckedPackage , buildImmutableCandidate , snapshotCommit } from "./checked-build.js" ;
2026-09-13 22:07:18 -07:00
import { formatQuixosLock , loadQuixosLock , parseQuixosLockDocument } from "../resource-lock/index.js" ;
2026-09-14 10:26:11 -07:00
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" ;
2026-09-13 22:07:18 -07:00
const authorSource = async ( root : string ) => {
2026-09-14 10:26:11 -07:00
const result = spawnSync ( "git" , [ "config" , "--get" , "remote.origin.url" ], { cwd : root , encoding : "utf8" });
2026-09-13 22:07:18 -07:00
if ( result . error || result . status !== 0 ) throw new Error ( "Managed resource has no origin" );
return { repository : result . stdout . trim (), commit : await snapshotCommit ( root )};
};
2026-09-08 14:27:24 -07:00
2026-09-14 10:26:11 -07:00
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 < 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 })),
note : "Structural plan only, not implementation verification. Run qx-workspace check while iterating." ,
});
2026-09-08 14:27:24 -07:00
const main = async () => {
const [ command , ... args ] = process . argv . slice ( 2 );
2026-09-15 13:45:42 -07:00
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 ;
}
2026-09-14 20:35:53 -07:00
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 ;
}
2026-09-14 10:26:11 -07:00
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" ) {
2026-09-15 13:45:42 -07:00
console . error ( `[ ${ new Date (). toISOString () } ] Capture: waiting for coordinator lock (120s limit)` );
2026-09-14 20:35:53 -07:00
const result = spawnSync ( "flock" , [ "--exclusive" , "--timeout" , "120" , "--conflict-exit-code" , "75" , path . join ( context . workbench , ".quixos/converge.lock" ),
2026-09-14 10:26:11 -07:00
process . execPath , process . argv [ 1 ], "_converge" , context . workbench , ...( args [ 1 ] ? [ args [ 1 ]] : [])], { stdio : "inherit" });
if ( result . error ) throw result . error ;
2026-09-14 20:35:53 -07:00
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" );
2026-09-14 10:26:11 -07:00
process . exitCode = result . status ?? 1 ; return ;
}
2026-09-15 13:45:42 -07:00
console . error ( `[ ${ new Date (). toISOString () } ] Capture: coordinator lock acquired` );
2026-09-14 10:26:11 -07:00
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" ]. 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" );
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 ;
}
2026-09-13 22:07:18 -07:00
if ( command === "scaffold-dependency" ) {
2026-09-14 10:26:11 -07:00
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 ) {
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 ;
}
2026-09-13 22:07:18 -07:00
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 ;}
}
if ( ! entrypoint ) throw new Error ( "No QX repository entrypoint" );
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 ;
}
2026-09-14 20:35:53 -07:00
const request : StructuralRequest = { kind : entrypoint , source : await authorSource ( root ), validation : "syntax" , files : [
2026-09-13 22:07:18 -07:00
{ 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 );
2026-09-14 10:26:11 -07:00
process . stdout . write ( ` ${ JSON . stringify ({... planSummary ( plan ), applied : flags . includes ( "--write" ) ? await applyStructure ( plan ) : undefined } , null, 2)}\n` );
2026-09-13 22:07:18 -07:00
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]" );
2026-09-14 10:26:11 -07:00
const spec = await readSpec ( specFile ) as ScaffoldRecipe ;
2026-09-13 22:07:18 -07:00
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 : [
2026-09-15 13:45:42 -07:00
{ file : "interface.qx" , create : spec . declaration ?? `interface ${ spec . name } id ${ JSON . stringify ( spec . id ) } revision ${ JSON . stringify ( spec . revision ) } {\n}\n` },
2026-09-13 22:07:18 -07:00
{ 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 );
2026-09-14 10:26:11 -07:00
process . stdout . write ( ` ${ JSON . stringify ({... planSummary ( plan ), applied : flags . includes ( "--write" ) ? await applyStructure ( plan ) : undefined } , null, 2)}\n` );
2026-09-13 22:07:18 -07:00
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 ;
}
2026-09-10 18:27:41 -07:00
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 ;
}
2026-09-14 20:35:53 -07:00
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 )) {
2026-09-10 18:27:41 -07:00
const [ root , specFile , ... flags ] = args ;
2026-09-13 22:07:18 -07:00
let spec : ScaffoldRecipe ;
if ( command === "scaffold-function" && /^[A-Za-z_][A-Za-z0-9_]*$/ . test ( specFile ?? "" )) {
2026-09-14 20:35:53 -07:00
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 } ` };
2026-09-13 22:07:18 -07:00
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" );
2026-09-14 10:26:11 -07:00
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 ));
2026-09-13 22:07:18 -07:00
flags . splice ( declaration , 2 );
}
2026-09-14 10:26:11 -07:00
} else spec = await readSpec ( specFile ) as ScaffoldRecipe ;
2026-09-10 18:27:41 -07:00
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]]" );
2026-09-14 20:35:53 -07:00
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 ;
2026-09-10 18:27:41 -07:00
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" );
2026-09-13 22:07:18 -07:00
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 ) {
2026-09-10 18:27:41 -07:00
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 } ` );
}
2026-09-13 22:07:18 -07:00
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 ]});
if ( locked . error || locked . status !== 0 ) throw new Error ( "Scaffold files retained; nix flake lock failed" );
2026-09-10 18:27:41 -07:00
}
2026-09-14 20:35:53 -07:00
process . stdout . write ( ` ${ JSON . stringify ({... plan ? planSummary ( plan ) : { installed : true } , applied}, null, 2)}\n` );
2026-09-10 18:27:41 -07:00
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]" );
2026-09-14 10:26:11 -07:00
const request = await readSpec ( spec ) as StructuralRequest ;
if ( request . kind !== "workspace" && ! request . source ) request . source = await authorSource ( root );
2026-09-10 18:27:41 -07:00
const plan = await planStructure ( root , request , process . env . QUIXOS_SNAPSHOT_MAP );
const applied = flags . includes ( "--write" ) ? await applyStructure ( plan ) : undefined ;
2026-09-14 10:26:11 -07:00
process . stdout . write ( ` ${ JSON . stringify ({... planSummary ( plan ), applied } , null, 2)}\n` );
2026-09-10 18:27:41 -07:00
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 ;
}
2026-09-14 10:26:11 -07:00
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" );
2026-09-10 18:27:41 -07:00
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 ;
}
2026-09-08 14:27:24 -07:00
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 ; });