import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, it } from "vitest"; import { discoverDocumentBundles, discoverProjectRoot, indexBundleDocuments, PHASE_BYTE_LIMIT, parseAuthoredDocument, parseDocumentId, parseSystemConfig, requireProjectRoot, resolveGovernedPath, SystemDocumentError, systemConfigForInitialization, validateBundleCandidate, validateDocumentPath, } from "../documents.ts"; const roots: string[] = []; const tree = () => { const root = mkdtempSync(path.join(tmpdir(), "the-system-documents-")); roots.push(root); return root; }; const dir = (root: string, ...parts: string[]) => { const target = path.join(root, ...parts); mkdirSync(target, { recursive: true }); return target; }; const write = (root: string, relative: string, content: string) => { const target = path.join(root, relative); dir(path.dirname(target)); writeFileSync(target, content); }; const source = ( directory: string, id: string, slug: string, frontmatter: string, ) => ({ path: `.system/${directory}/${id}-${slug}/index.md`, markdown: `---\n${frontmatter}\n---\n\n# ${id}\n`, }); const spec = (id = "PX-SPEC-ABCDEFGH") => `---\nid: ${id}\ntype: spec\ntitle: Contract\n---\n\n# ${id}\n`; afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); }); describe("project root and schema-0 config", () => { it("resolves the nearest .system root", () => { const root = tree(); dir(root, ".system"); const nested = dir(root, "nested"); assert.equal(discoverProjectRoot(nested), root); dir(nested, ".system"); assert.equal(discoverProjectRoot(nested), nested); assert.equal(resolveGovernedPath(root, "x/../x"), path.join(root, "x")); assert.throws( () => resolveGovernedPath(root, "../outside"), SystemDocumentError, ); }); it("returns undefined without a .system directory", () => { assert.equal(discoverProjectRoot(tree()), undefined); assert.throws(() => requireProjectRoot(tree()), SystemDocumentError); }); it("ignores .system files and symbolic links while walking", () => { const root = tree(); dir(root, ".system"); dir(root, "elsewhere"); const fileDirectory = dir(root, "file"); writeFileSync(path.join(fileDirectory, ".system"), ""); const linkDirectory = dir(root, "link"); symlinkSync( path.join(root, "elsewhere"), path.join(linkDirectory, ".system"), "dir", ); assert.equal(discoverProjectRoot(fileDirectory), root); assert.equal(discoverProjectRoot(linkDirectory), root); }); it("accepts schema 0 and prepares mismatched config for initialization", () => { assert.deepEqual( parseSystemConfig( '{"prefix":"PX","schemaVersion":0,"title":"Project X","currentPhase":"PX-PHASE-PHASE001"}', ), { prefix: "PX", schemaVersion: 0, title: "Project X", currentPhase: "PX-PHASE-PHASE001", }, ); assert.deepEqual(parseSystemConfig('{"prefix":"PX","schemaVersion":0}'), { prefix: "PX", schemaVersion: 0, }); assert.throws( () => parseSystemConfig('{"prefix":"PX","schemaVersion":4}'), /does not match 0; run \/system init/, ); assert.deepEqual( systemConfigForInitialization( '{"prefix":"PX","schemaVersion":4,"title":"Project X"}', ), { config: { prefix: "PX", schemaVersion: 0, title: "Project X" }, sourceSchemaVersion: 4, }, ); }); }); describe("schema-0 identity and metadata", () => { it("parses uppercase token IDs and rejects short numeric IDs", () => { assert.deepEqual(parseDocumentId("PX-SPEC-AZ3_P9QK", "PX"), { id: "PX-SPEC-AZ3_P9QK", prefix: "PX", type: "spec", token: "AZ3_P9QK", }); assert.throws( () => parseDocumentId("PX-SPEC-001", "PX"), SystemDocumentError, ); assert.throws( () => parseDocumentId("PX-SPEC-az3_p9qk", "PX"), SystemDocumentError, ); }); it("projects known graph fields while preserving unknown frontmatter", () => { const parsed = parseAuthoredDocument( `---\nid: PX-PLAN-PL4N0001\ntype: plan\ntitle: Delivery\nspec: PX-SPEC-SPEC0001\nstatus: draft\ndepends_on: [PX-PLAN-PL4N0000]\nreview: human\n---\n`, "PX", ); assert.deepEqual(parsed.metadata, { id: "PX-PLAN-PL4N0001", type: "plan", title: "Delivery", spec: "PX-SPEC-SPEC0001", status: "draft", depends_on: ["PX-PLAN-PL4N0000"], }); assert.deepEqual( [...parsed.frontmatter.keys()], ["id", "type", "title", "spec", "status", "depends_on", "review"], ); }); it("parses operating phases and validates their labels and size", () => { assert.deepEqual( parseAuthoredDocument( "---\nid: PX-PHASE-PHASE001\ntype: phase\ntitle: Pre-v1\nlabel: pre-v1\n---\n", "PX", ).metadata, { id: "PX-PHASE-PHASE001", type: "phase", title: "Pre-v1", label: "pre-v1", }, ); assert.throws( () => parseAuthoredDocument( "---\nid: PX-PHASE-PHASE001\ntype: phase\ntitle: Pre-v1\nlabel: label-is-too-long\n---\n", "PX", ), /1-12 ASCII/, ); assert.throws( () => parseAuthoredDocument( `---\nid: PX-PHASE-PHASE001\ntype: phase\ntitle: Pre-v1\nlabel: pre-v1\n---\n\n${"x".repeat(PHASE_BYTE_LIMIT)}`, "PX", ), /byte limit/, ); }); it("parses issue metadata without status and validates approval status", () => { assert.deepEqual( parseAuthoredDocument( "---\nid: PX-ISSUE-ISSUE001\ntype: issue\ntitle: Bug\nspecs: []\n---\n", "PX", ).metadata, { id: "PX-ISSUE-ISSUE001", type: "issue", title: "Bug", specs: [], }, ); assert.equal( parseAuthoredDocument( "---\nid: PX-PLAN-PLAN0001\ntype: plan\ntitle: Build\nspec: PX-SPEC-SPEC0001\nstatus: approved\n---\n", "PX", ).metadata.status, "approved", ); assert.throws( () => parseAuthoredDocument( "---\nid: PX-PLAN-PLAN0001\ntype: plan\ntitle: Build\nspec: PX-SPEC-SPEC0001\nstatus: done\n---\n", "PX", ), /status must be draft or approved/, ); }); it("accepts only canonical bundle paths", () => { const metadata = { id: "PX-SPEC-AZ3_P9QK", type: "spec", title: "Login", } as const; assert.doesNotThrow(() => validateDocumentPath( ".system/specs/PX-SPEC-AZ3_P9QK-login-flow/index.md", metadata, "PX", ), ); for (const item of [ ".system/specs/PX-SPEC-AZ3_P9QK-login-flow.md", ".system/phases/PX-SPEC-AZ3_P9QK-login-flow/index.md", ".system/specs/PX-SPEC-AZ3_P9QK-Login/index.md", ".system/specs/PX-SPEC-AZ3_P9QK-login-flow/other/index.md", ]) assert.throws( () => validateDocumentPath(item, metadata, "PX"), SystemDocumentError, ); }); }); describe("bundle discovery and graph", () => { it("returns authored indexes and structural diagnostics separately", () => { const root = tree(); write( root, ".system/specs/PX-SPEC-SPEC0001-contract/index.md", spec("PX-SPEC-SPEC0001"), ); write( root, ".system/specs/PX-SPEC-SPEC0001-contract/assets/data.csv", "a,b", ); write(root, ".system/plans/loose.md", "bad"); dir(root, ".system", "specs", "PX-SPEC-SPEC0002-empty"); assert.equal(discoverDocumentBundles(root).sources.length, 1); assert.deepEqual( discoverDocumentBundles(root).diagnostics.map( (item) => `${item.path} ${item.code}`, ), [ ".system/plans/loose.md bundle.expected-directory", ".system/specs/PX-SPEC-SPEC0002-empty bundle.missing-index", ], ); }); it("reports symlinks without following them", () => { const root = tree(); const outside = tree(); write( root, ".system/specs/PX-SPEC-SPEC0001-contract/index.md", spec("PX-SPEC-SPEC0001"), ); symlinkSync( outside, path.join(root, ".system/specs/PX-SPEC-SPEC0002-link"), "dir", ); symlinkSync( outside, path.join(root, ".system/specs/PX-SPEC-SPEC0001-contract/assets-link"), "dir", ); assert.deepEqual( discoverDocumentBundles(root).diagnostics.map((item) => item.code), ["bundle.symbolic-link", "bundle.symbolic-link"], ); }); it("keeps phases separate from the spec-plan graph", () => { const indexed = indexBundleDocuments( [ source( "research", "PX-RESEARCH-RESEARCH", "notes", "id: PX-RESEARCH-RESEARCH\ntype: research\ntitle: Notes", ), source( "specs", "PX-SPEC-SPEC0001", "one", "id: PX-SPEC-SPEC0001\ntype: spec\ntitle: One\nresearch: [PX-RESEARCH-RESEARCH]", ), source( "specs", "PX-SPEC-SPEC0002", "two", "id: PX-SPEC-SPEC0002\ntype: spec\ntitle: Two", ), source( "phases", "PX-PHASE-PHASE001", "delivery", "id: PX-PHASE-PHASE001\ntype: phase\ntitle: Delivery\nlabel: delivery", ), source( "plans", "PX-PLAN-PLAN0001", "build", "id: PX-PLAN-PLAN0001\ntype: plan\ntitle: Build\nspec: PX-SPEC-SPEC0002\nstatus: draft", ), ], "PX", ); assert.deepEqual(indexed.diagnostics, []); assert.deepEqual( indexed.documents.find((document) => document.metadata.type === "phase") ?.metadata, { id: "PX-PHASE-PHASE001", type: "phase", title: "Delivery", label: "delivery", }, ); }); it("validates dependency references and cycles", () => { const indexed = indexBundleDocuments( [ source( "specs", "PX-SPEC-SPEC0001", "one", "id: PX-SPEC-SPEC0001\ntype: spec\ntitle: One", ), source( "specs", "PX-SPEC-SPEC0002", "two", "id: PX-SPEC-SPEC0002\ntype: spec\ntitle: Two", ), source( "plans", "PX-PLAN-PLAN0001", "one", "id: PX-PLAN-PLAN0001\ntype: plan\ntitle: One\nspec: PX-SPEC-SPEC0001\nstatus: draft\ndepends_on: [PX-PLAN-MISSING1]", ), source( "plans", "PX-PLAN-PLAN0002", "two", "id: PX-PLAN-PLAN0002\ntype: plan\ntitle: Two\nspec: PX-SPEC-SPEC0001\nstatus: draft\ndepends_on: [PX-PLAN-PLAN0003]", ), source( "plans", "PX-PLAN-PLAN0003", "three", "id: PX-PLAN-PLAN0003\ntype: plan\ntitle: Three\nspec: PX-SPEC-SPEC0001\nstatus: draft\ndepends_on: [PX-PLAN-PLAN0002]", ), source( "plans", "PX-PLAN-PLAN0004", "four", "id: PX-PLAN-PLAN0004\ntype: plan\ntitle: Four\nspec: PX-SPEC-SPEC0002\nstatus: draft\ndepends_on: [PX-PLAN-PLAN0002]", ), ], "PX", ); assert.deepEqual(indexed.diagnostics.map((item) => item.code).sort(), [ "graph.dangling-reference", "graph.dependency-cycle", "graph.forbidden-edge", ]); }); it("candidate validation reports only new candidate findings", () => { const findings = validateBundleCandidate( [ source( "specs", "PX-SPEC-SPEC0001", "one", "id: PX-SPEC-SPEC0001\ntype: spec\ntitle: One", ), ], source( "plans", "PX-PLAN-PLAN0001", "delivery", "id: PX-PLAN-PLAN0001\ntype: plan\ntitle: Delivery\nspec: PX-SPEC-MISSING1\nstatus: draft", ), "PX", ); assert.deepEqual( findings.map((item) => item.code), ["graph.dangling-reference"], ); }); });