import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { SCAFFOLDS, renderScaffoldDocument } from "../src/tools/doc-init.ts"; import { validateLinks } from "../src/utils.ts"; const FIXED_DATE = "2026-01-01"; function scaffoldFor(path: string) { const sc = SCAFFOLDS.find((s) => s.path === path); assert.ok(sc, `no scaffold entry for ${path}`); return sc!; } const LINK_RE = /\[[^\]]+\]\(([^)]+)\)/g; test("scaffold: every generated document links relative to its own file and all targets exist", () => { const dir = mkdtempSync(join(tmpdir(), "docgraph-scaffold-")); try { const rendered = new Map(); // docgraph_init ensures these directories before writing any docs mkdirSync(join(dir, "docs", "tickets"), { recursive: true }); for (const sc of SCAFFOLDS) { const content = renderScaffoldDocument(sc, { lastUpdated: FIXED_DATE }); rendered.set(sc.path, content); const out = join(dir, sc.path); mkdirSync(out.slice(0, out.lastIndexOf("/")), { recursive: true }); writeFileSync(out, content); } for (const [path, content] of rendered) { const res = validateLinks(content, path, dir); assert.deepEqual( res.broken, [], `${path} contains broken cross-references: ${res.broken.join(", ")}`, ); } } finally { rmSync(dir, { recursive: true, force: true }); } }); test("scaffold: nothing under docs/ uses a root-relative docs/ href", () => { for (const sc of SCAFFOLDS.filter((s) => s.path.startsWith("docs/"))) { const content = renderScaffoldDocument(sc, { lastUpdated: FIXED_DATE }); for (const m of content.matchAll(LINK_RE)) { const href = m[1]!; assert.ok( !href.startsWith("docs/"), `${sc.path} links to root-relative ${href}; use a file-relative href instead`, ); } } }); test("scaffold: spec examples — README keeps docs/SPEC.md, docs/ARCHITECTURE.md uses SPEC.md", () => { const readme = renderScaffoldDocument(scaffoldFor("README.md"), { lastUpdated: FIXED_DATE, }); assert.match(readme, /\[SPEC\]\(docs\/SPEC\.md\)/); const architecture = renderScaffoldDocument(scaffoldFor("docs/ARCHITECTURE.md"), { lastUpdated: FIXED_DATE, }); assert.match(architecture, /\[SPEC\]\(SPEC\.md\)/); // Sibling docs inside docs/ never carry a docs/ prefix assert.match(architecture, /\[API\]\(API\.md\)/); assert.doesNotMatch(architecture, /\]\(docs\//); // docs/SPEC.md depends on AGENTS.md at the repo root, one level up const spec = renderScaffoldDocument(scaffoldFor("docs/SPEC.md"), { lastUpdated: FIXED_DATE, }); assert.match(spec, /\[AGENTS\]\(\.\.\/AGENTS\.md\)/); }); test("scaffold: ROADMAP links BACKLOG relative to docs/", () => { const roadmap = renderScaffoldDocument(scaffoldFor("docs/ROADMAP.md"), { lastUpdated: FIXED_DATE, }); assert.match(roadmap, /\[BACKLOG\]\(BACKLOG\.md\)/); }); test("scaffold: AGENTS.md index links docs/* from the repo root", () => { const agents = renderScaffoldDocument(scaffoldFor("AGENTS.md"), { lastUpdated: FIXED_DATE, }); assert.match(agents, /\[README\]\(README\.md\)/); assert.match(agents, /\[SPEC\]\(docs\/SPEC\.md\)/); assert.match(agents, /\[BACKLOG\]\(docs\/BACKLOG\.md\)/); });