Make workspace authoring converge through immutable Nix candidates
Coordinate registered resource edits bottom-up into retained exact remote sources. Use one Nix-owned source graph for provisional checking, template publication, explicit baseline upgrades and host activation; retain independent runtime pins. Add scoped contract inspection, historical recovery, derived worklists, crash-safe locks, named dependency adoption and plain-QX structural editing. Repair TODO ownership and template instantiation, and document the supported agent workflow. Validated with protocol and command suites, real jj/Nix convergence and cache checks, TS/React installed-command acceptance, and fresh TODO first-edit acceptance. No live deployment or public publication performed. Props projection generation and a one-command rich feature generator remain explicitly outside this delivery.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { readFile, realpath } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { loadQuixosLock, type GitSource } from "../resource-lock/index.js";
|
||||
|
||||
export type AuthoringResource = {
|
||||
kind: "workspace" | "interface" | "package";
|
||||
directory: string;
|
||||
resourceId?: string;
|
||||
source?: GitSource;
|
||||
};
|
||||
|
||||
/** Registration, not directory co-location, defines the editable selection. */
|
||||
export async function authoringContext(start: string) {
|
||||
let workbench = await realpath(start);
|
||||
for (;;) {
|
||||
let text: string | undefined;
|
||||
try { text = await readFile(path.join(workbench, ".quixos/resource-graph.json"), "utf8"); }
|
||||
catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; }
|
||||
if (text !== undefined) {
|
||||
const graph = JSON.parse(text) as { resources: AuthoringResource[] };
|
||||
if (!Array.isArray(graph.resources)) throw new Error("Managed resource inventory is malformed");
|
||||
const resources: AuthoringResource[] = [{ kind: "workspace", directory: "root" }];
|
||||
const identities = new Set<string>(), directories = new Set<string>(["root"]);
|
||||
for (const entry of graph.resources) {
|
||||
// Compiler graphs carry resolved paths; the authoring API presents
|
||||
// stable workbench-relative names and validates containment here.
|
||||
if (typeof entry.directory === "string" && path.isAbsolute(entry.directory)) entry.directory = path.relative(workbench, entry.directory);
|
||||
if (!["interface", "package"].includes(entry.kind) || !entry.source ||
|
||||
!/^resources\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(entry.directory)) {
|
||||
throw new Error("Invalid managed resource registration");
|
||||
}
|
||||
const identity = `${entry.kind}\0${entry.resourceId ?? entry.source.repository}`;
|
||||
if (identities.has(identity) || directories.has(entry.directory)) {
|
||||
throw new Error(`Multiple editable selections for ${entry.resourceId ?? entry.source.repository}`);
|
||||
}
|
||||
identities.add(identity); directories.add(entry.directory);
|
||||
resources.push({kind: entry.kind, directory: entry.directory, resourceId: entry.resourceId, source: entry.source});
|
||||
}
|
||||
return { workbench, resources, async baseline() {
|
||||
const result = await loadQuixosLock(path.join(workbench, "root/quixos.lock"));
|
||||
if (!result.ok) throw new Error(`Workspace source baseline is invalid: ${result.diagnostics.map(d => d.message).join("; ")}`);
|
||||
return result.lock.quixos;
|
||||
} };
|
||||
}
|
||||
const parent = path.dirname(workbench);
|
||||
if (parent === workbench) throw new Error("Not in a managed workbench; select one with --workbench DIRECTORY");
|
||||
workbench = parent;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user