import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, it } from "vitest"; import { runDoctor } from "../doctor.ts"; import { SystemDocumentError } from "../documents.ts"; import { applyInitialization, inspectInitialization } from "../init.ts"; const roots: string[] = []; const root = () => { const value = mkdtempSync(path.join(tmpdir(), "the-system-init-")); roots.push(value); return value; }; const write = (project: string, relative: string, content: string) => { const target = path.join(project, relative); mkdirSync(path.dirname(target), { recursive: true }); writeFileSync(target, content, "utf8"); }; const cores = (project: string) => { for (const name of ["SYSTEM.md", "MISSION.md", "RULES.md"]) write(project, `.system/${name}`, `# ${name}\n\nApproved.\n`); }; afterEach(() => { for (const value of roots.splice(0)) rmSync(value, { recursive: true, force: true }); }); describe("guided initialization machine state", () => { it("creates schema-0 config and core files, then preserves cores on rerun", () => { const project = root(); const cwd = path.join(project, "packages", "app"); mkdirSync(cwd, { recursive: true }); const script = path.resolve(import.meta.dirname, "../scripts/init.mjs"); const child = spawnSync( process.execPath, ["--experimental-strip-types", script, "apply", cwd, "PX"], { encoding: "utf8", env: { ...process.env, NODE_NO_WARNINGS: "1" } }, ); assert.equal(child.status, 0); assert.match(child.stdout, /WROTE \.system\/config\.json/); assert.match(child.stdout, /WROTE \.system\/board\.json/); assert.match(child.stdout, /WROTE \.system\/SYSTEM\.md/); assert.deepEqual( JSON.parse(readFileSync(path.join(cwd, ".system/config.json"), "utf8")), { prefix: "PX", schemaVersion: 0, title: path.basename(cwd) }, ); assert.deepEqual( JSON.parse(readFileSync(path.join(cwd, ".system/board.json"), "utf8")), { schemaVersion: 0, items: {} }, ); assert.match( readFileSync(path.join(cwd, ".system/SYSTEM.md"), "utf8"), /Stable project identity/, ); assert.match( readFileSync(path.join(cwd, ".system/MISSION.md"), "utf8"), /Enduring high-level project objective/, ); assert.match( readFileSync(path.join(cwd, ".system/RULES.md"), "utf8"), /Concrete operating constraints/, ); for (const directory of ["specs", "phases", "plans", "issues", "research"]) assert.equal( lstatSync(path.join(cwd, ".system", directory), { throwIfNoEntry: false, }), undefined, ); const before = ["SYSTEM.md", "MISSION.md", "RULES.md"].map((name) => { const target = path.join(cwd, ".system", name); return [name, readFileSync(target, "utf8"), lstatSync(target).mtimeMs]; }); assert.deepEqual(applyInitialization(cwd, "PX"), { root: cwd, prefix: "PX", changed: [], pending: [], }); for (const [name, source, mtime] of before) { const target = path.join(cwd, ".system", name); assert.deepEqual( [readFileSync(target, "utf8"), lstatSync(target).mtimeMs], [source, mtime], ); } assert.equal(runDoctor(cwd).exitCode, 0); }); it("holds a mismatched config pending, then rewrites it when approved", () => { const project = root(); const mismatched = '{"prefix":"PX","schemaVersion":4,"title":"Project","obsolete":true}\n'; write(project, ".system/config.json", mismatched); cores(project); const rewrites = [".system/config.json schemaVersion 4 -> 0"]; assert.deepEqual(inspectInitialization(project).rewrites, rewrites); assert.deepEqual(applyInitialization(project).pending, rewrites); assert.equal( readFileSync(path.join(project, ".system/config.json"), "utf8"), mismatched, ); const result = applyInitialization(project, undefined, true); assert.deepEqual(result.changed, [".system/config.json"]); assert.deepEqual( JSON.parse( readFileSync(path.join(project, ".system/config.json"), "utf8"), ), { prefix: "PX", schemaVersion: 0, title: "Project" }, ); }); it("rewrites a mismatched board directly to schema 0", () => { const project = root(); applyInitialization(project, "PX"); write( project, ".system/board.json", JSON.stringify({ schemaVersion: 7, items: { "PX-SPEC-SPEC0001": { state: "done", updatedAt: "2026-03-20T12:00:00.000Z", source: "manual", }, }, }), ); assert.deepEqual(inspectInitialization(project).rewrites, [ ".system/board.json schemaVersion 7 -> 0", ]); const configBefore = readFileSync( path.join(project, ".system/config.json"), "utf8", ); const result = applyInitialization(project, undefined, true); assert.deepEqual(result.changed, [".system/board.json"]); assert.equal( readFileSync(path.join(project, ".system/config.json"), "utf8"), configBefore, ); assert.equal( JSON.parse(readFileSync(path.join(project, ".system/board.json"), "utf8")) .schemaVersion, 0, ); }); it("preserves selected phase and title while resetting the schema", () => { const project = root(); applyInitialization(project, "PX"); write( project, ".system/config.json", JSON.stringify({ prefix: "PX", schemaVersion: 4, title: "Custom title", currentPhase: "PX-PHASE-PHASE001", }), ); applyInitialization(project, undefined, true); assert.deepEqual( JSON.parse( readFileSync(path.join(project, ".system/config.json"), "utf8"), ), { prefix: "PX", schemaVersion: 0, title: "Custom title", currentPhase: "PX-PHASE-PHASE001", }, ); }); it("rejects invalid prefixes, prefix conflicts, and unsafe config paths", () => { const project = root(); assert.throws(() => applyInitialization(project), /prefix is required/i); assert.throws(() => applyInitialization(project, "x"), SystemDocumentError); applyInitialization(project, "PX"); assert.throws( () => inspectInitialization(project, "AB"), /prefix.*does not match/i, ); const linked = root(); mkdirSync(path.join(linked, ".system")); symlinkSync("elsewhere.json", path.join(linked, ".system/config.json")); assert.throws( () => inspectInitialization(linked), (error: unknown) => error instanceof SystemDocumentError && error.code === "init.unsafe-path", ); }); });