// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // // The licence layout (plan item AT5.1, spec section 14.3): LICENSE-TEMPLATE says what it is, the // customer's LICENSE is a stub, NOTICE and TEMPLATE-FILES exist and agree with the tree, and every // template file that can hold a comment carries the SPDX header. The negative controls make the check // fail where it should: a scaffold file that loses its header, a file nobody listed, a listed file that // is gone. import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { dirname, join } from "node:path" import { afterEach, describe, expect, it } from "vitest" import { hasHeader, headerFor, LIST_FILE, listText, parseList, problems, SPDX_ID, SPDX_LINE, templateFiles, withHeader, write, } from "../scripts/licence.mjs" const APP_ROOT = new URL("..", import.meta.url).pathname const read = (path: string) => readFileSync(join(APP_ROOT, path), "utf8") const made: string[] = [] afterEach(() => { for (const dir of made.splice(0)) rmSync(dir, { recursive: true, force: true }) }) /** A small tree shaped like the template: a scaffold, a module and the layout's own files. */ function fixture(files: Record): string { const root = mkdtempSync(join(tmpdir(), "licence-")) made.push(root) const all: Record = { LICENSE: "yours\n", "LICENSE-TEMPLATE": "placeholder\n", NOTICE: "notice\n", "THIRD-PARTY-NOTICES": "none\n", "package.json": "{}\n", "src/a.ts": "export const a = 1\n", "modules/notes/index.ts": "export const notes = 1\n", ...files, } for (const [path, text] of Object.entries(all)) { mkdirSync(dirname(join(root, path)), { recursive: true }) writeFileSync(join(root, path), text) } write(root) return root } describe("this template's licence layout", () => { it("is whole: every file is listed, every file that can hold a header has it, and the four documents exist", () => { expect(problems(APP_ROOT)).toEqual([]) }) it("says in LICENSE-TEMPLATE that it is a placeholder, until counsel decides (AT-O2)", () => { const text = read("LICENSE-TEMPLATE") expect(text).toMatch(/PLACEHOLDER/) expect(text).toContain("AT-O2") expect(text).toContain(SPDX_ID) expect(text).toMatch(/counsel/i) }) it("keeps LICENSE the customer's own, a stub that names what it does not cover", () => { const text = read("LICENSE") expect(text).toMatch(/This file is yours/) expect(text).toContain("LICENSE-TEMPLATE") expect(text).toContain("TEMPLATE-FILES") }) it("lists the template's files and not the customer's", () => { const listed = parseList(read(LIST_FILE)) expect(listed).toEqual(templateFiles(APP_ROOT)) expect(listed).toContain("apps/web/app/layout.tsx") expect(listed).toContain("LICENSE-TEMPLATE") expect(listed).toContain(LIST_FILE) expect(listed).not.toContain("LICENSE") expect(listed).not.toContain("pnpm-lock.yaml") expect(listed.some((path) => path.startsWith("modules/"))).toBe(false) expect(listed).not.toContain(".conusai/template.json") }) it("puts the header first in a TypeScript file, a kernel migration, a workflow, the Dockerfile and the guide", () => { expect(read("Dockerfile").split("\n")[0]).toBe(`# ${SPDX_LINE}`) expect(read("test/registry.test.ts").split("\n")[0]).toBe(`// ${SPDX_LINE}`) expect(read("migrations/postgres/0001_kernel.sql").split("\n")[0]).toBe(`-- ${SPDX_LINE}`) expect(read(".gitea/workflows/ci.yml").split("\n")[0]).toBe(`# ${SPDX_LINE}`) expect(read("AGENTS.md").split("\n")[0]).toBe(``) }) }) describe("the header", () => { it("is a comment of the file's own kind, and none for a file that cannot hold one", () => { expect(headerFor("a/b.ts")).toBe(`// ${SPDX_LINE}`) expect(headerFor("a/b.mjs")).toBe(`// ${SPDX_LINE}`) expect(headerFor("a/b.css")).toBe(`/* ${SPDX_LINE} */`) expect(headerFor("a/b.sql")).toBe(`-- ${SPDX_LINE}`) expect(headerFor("a/b.md")).toBe(``) expect(headerFor("compose.yaml")).toBe(`# ${SPDX_LINE}`) expect(headerFor("Dockerfile")).toBe(`# ${SPDX_LINE}`) expect(headerFor(".env.example")).toBe(`# ${SPDX_LINE}`) expect(headerFor("package.json")).toBeNull() expect(headerFor("llms.txt")).toBeNull() expect(headerFor("LICENSE-TEMPLATE")).toBeNull() }) it("goes after a shebang, and is added once", () => { const script = "#!/usr/bin/env bash\nset -e\n" const once = withHeader(script, "run.sh") expect(once).toBe(`#!/usr/bin/env bash\n# ${SPDX_LINE}\nset -e\n`) expect(withHeader(once, "run.sh")).toBe(once) expect(hasHeader(once, "run.sh")).toBe(true) expect(hasHeader(script, "run.sh")).toBe(false) }) it("goes after the frontmatter of a Markdown file that opens with one (an agent skill), and is added once", () => { const skill = "---\nname: fix\ndescription: Fix it.\n---\n# Fix\n" const once = withHeader(skill, ".claude/skills/fix/SKILL.md") expect(once).toBe(`---\nname: fix\ndescription: Fix it.\n---\n\n# Fix\n`) expect(withHeader(once, ".claude/skills/fix/SKILL.md")).toBe(once) expect(hasHeader(once, ".claude/skills/fix/SKILL.md")).toBe(true) // A header above the frontmatter would hide the skill from its tool, so it does not count. expect(hasHeader(`\n${skill}`, ".claude/skills/fix/SKILL.md")).toBe(true) expect(hasHeader(skill, ".claude/skills/fix/SKILL.md")).toBe(false) }) }) describe("the check fails where it should", () => { it("passes a tree that `write` has laid out, and ignores modules/** and LICENSE", () => { const root = fixture({}) expect(problems(root)).toEqual([]) expect(parseList(readFileSync(join(root, LIST_FILE), "utf8"))).toEqual([ "LICENSE-TEMPLATE", "NOTICE", "TEMPLATE-FILES", "THIRD-PARTY-NOTICES", "package.json", "src/a.ts", ]) expect(readFileSync(join(root, "modules/notes/index.ts"), "utf8")).not.toContain("SPDX") expect(readFileSync(join(root, "LICENSE"), "utf8")).toBe("yours\n") }) it("fails, naming the file, when a scaffold file loses its header", () => { const root = fixture({}) writeFileSync(join(root, "src/a.ts"), "export const a = 1\n") expect(problems(root)).toEqual([`src/a.ts has no "${SPDX_LINE}" header`]) }) it("fails when a file is not in TEMPLATE-FILES, and when a listed file is gone", () => { const root = fixture({}) writeFileSync(join(root, "src/new.ts"), `// ${SPDX_LINE}\nexport {}\n`) expect(problems(root)).toEqual(["TEMPLATE-FILES does not list src/new.ts"]) rmSync(join(root, "src/new.ts")) rmSync(join(root, "src/a.ts")) expect(problems(root)).toEqual(["TEMPLATE-FILES lists src/a.ts, which is not a template file"]) }) it("fails when one of the four documents is missing", () => { const root = fixture({}) rmSync(join(root, "NOTICE")) expect(problems(root)).toContain("NOTICE is missing") }) it("is what listText writes: comments, then one path a line", () => { const text = listText(["a", "b"]) expect(parseList(text)).toEqual(["a", "b"]) expect(text.startsWith("# ")).toBe(true) }) })