ConusAI vendor v0.1.0-dev.20261027+9b1131fb
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-dev.20261027+9b1131fb
2026-10-27 18:05:17 +00:00
commit 3102273ec7
538 changed files with 80203 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
//
// The licence layout of this template (plan item AT5.1, spec section 14.3), as a tool and as the
// rules `kit check` and the tests read.
//
// node scripts/licence.mjs --check fail, naming each file, when the layout is not whole
// node scripts/licence.mjs --write add the missing headers and rewrite TEMPLATE-FILES
//
// What is the template's and what is the customer's:
//
// - Template files (this tool's `templateFiles`) carry the SPDX header `LicenseRef-ConusAI-Template-1.0`
// and are listed in TEMPLATE-FILES: every file of the scaffold except `modules/**` and `test/modules/**` (the
// customer's domain code and its app-level tests, which carry the customer's licence), `LICENSE` (the customer's own file, a stub
// until they fill it in), `pnpm-lock.yaml` (generated per bundle and then maintained by the
// customer) and `.conusai/template.json` (the upgrade record, written by the bundle).
// - A file that cannot hold a comment (JSON, an image, llms.txt) is listed and exempt from
// the header: `headerFor` answers null for it.
//
// `kit check` of @conusai-kit/cli reads the same two facts, TEMPLATE-FILES and the header, so a file a
// customer or an agent copies out of the scaffold without its header fails the gate.
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"
import { dirname, join, relative, sep } from "node:path"
import { fileURLToPath } from "node:url"
export const SPDX_ID = "LicenseRef-ConusAI-Template-1.0"
export const SPDX_LINE = `SPDX-License-Identifier: ${SPDX_ID}`
export const LIST_FILE = "TEMPLATE-FILES"
/** Directories that are never part of the shipped template, at any depth. */
// `_generated`: Convex codegen output, written by a deploy and git-ignored.
const IGNORED_DIRS = new Set([
"node_modules",
".next",
"dist",
"test-results",
"playwright-report",
".logs",
".dev",
"_generated",
])
/** Files that are the customer's, or written by the bundle, and so are not the template's. */
const NOT_THE_TEMPLATES = new Set(["LICENSE", "pnpm-lock.yaml", ".conusai/template.json", ".env", ".env.local"])
const CUSTOMER_PREFIXES = ["modules/", "test/modules/"]
/** Files a tool writes: Next.js rewrites next-env.d.ts, TypeScript writes build info. They are not shipped. */
const GENERATED = (name) => name === "next-env.d.ts" || name.endsWith(".tsbuildinfo")
/** `//`-style comment types, by extension. */
const SLASH = new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs"])
const HASH = new Set([".yml", ".yaml", ".sh", ".npmrc", ".gitignore", ".dockerignore", ".example"])
const HASH_NAMES = new Set(["Dockerfile", ".npmrc", ".gitignore", ".dockerignore"])
const extensionOf = (path) => {
const name = path.slice(path.lastIndexOf("/") + 1)
const dot = name.lastIndexOf(".")
return dot <= 0 ? "" : name.slice(dot)
}
/**
* The comment that carries the header in this file, or null when the file cannot hold one. Plain text
* the licence layout itself writes (LICENSE-TEMPLATE, NOTICE, THIRD-PARTY-NOTICES, TEMPLATE-FILES) states
* its licence in its own words, so it is exempt too.
*/
export function headerFor(path) {
const name = path.slice(path.lastIndexOf("/") + 1)
const ext = extensionOf(path)
if (SLASH.has(ext)) return `// ${SPDX_LINE}`
if (ext === ".css") return `/* ${SPDX_LINE} */`
if (ext === ".sql") return `-- ${SPDX_LINE}`
if (ext === ".md") return `<!-- ${SPDX_LINE} -->`
if (HASH.has(ext) || HASH_NAMES.has(name) || name.startsWith(".env")) return `# ${SPDX_LINE}`
return null
}
/** Every file the template ships that is the template's, as sorted POSIX paths relative to `root`. */
export function templateFiles(root) {
const found = []
const walk = (dir) => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (!IGNORED_DIRS.has(entry.name)) walk(join(dir, entry.name))
} else if (entry.isFile() && !GENERATED(entry.name)) {
const rel = relative(root, join(dir, entry.name)).split(sep).join("/")
if (NOT_THE_TEMPLATES.has(rel) || CUSTOMER_PREFIXES.some((prefix) => rel.startsWith(prefix))) continue
found.push(rel)
}
}
}
walk(root)
return found.sort()
}
/** The text TEMPLATE-FILES holds for a set of paths. */
export function listText(paths) {
return [
"# The files of this template that ConusAI owns: the scaffold `kit template:upgrade` merges, and the",
`# files that carry the header \`${SPDX_ID}\` (LICENSE-TEMPLATE). One path per line.`,
"#",
"# Not listed, because they are yours: `modules/**` (your domain code), `test/modules/**` (its tests that",
"# need the whole app), `LICENSE` (your own licence), `pnpm-lock.yaml` (your own lock) and",
"# `.conusai/template.json` (the upgrade record). A file that",
"# cannot hold a comment (JSON, llms.txt) is listed and has no header. Regenerate with",
"# `node scripts/licence.mjs --write`.",
...paths,
"",
].join("\n")
}
/** The paths a TEMPLATE-FILES text lists. */
export function parseList(text) {
return text
.split("\n")
.map((line) => line.trim())
.filter((line) => line !== "" && !line.startsWith("#"))
}
/**
* The line the header sits on: after a shebang, and after the YAML frontmatter of a Markdown file that has one
* (an agent skill or command, `.claude/skills/<name>/SKILL.md`, must open with `---` for its tool to read it). A
* BOM or a directive's line is not skipped: the header leads. Keep frontmatter short: `kit check` looks for the
* header in a file's first eight lines.
*/
function headerLine(lines, path) {
if (lines[0]?.startsWith("#!")) return 1
if (extensionOf(path) === ".md" && lines[0]?.trim() === "---") {
const close = lines.indexOf("---", 1)
if (close > 0) return close + 1
}
return 0
}
/** Whether `text` carries the header on its header line (see `headerLine`). */
export function hasHeader(text, path) {
const header = headerFor(path)
if (header === null) return true
const lines = text.replace(/^/, "").split("\n")
return lines[headerLine(lines, path)]?.trim() === header
}
/** The text with the header added on its header line (see `headerLine`). */
export function withHeader(text, path) {
const header = headerFor(path)
if (header === null || hasHeader(text, path)) return text
const lines = text.split("\n")
lines.splice(headerLine(lines, path), 0, header)
return lines.join("\n")
}
/**
* Everything wrong with the licence layout under `root`, as one sentence each. Empty when whole.
*/
export function problems(root) {
const found = []
for (const required of ["LICENSE-TEMPLATE", "LICENSE", "NOTICE", LIST_FILE, "THIRD-PARTY-NOTICES"]) {
if (!existsSync(join(root, required))) found.push(`${required} is missing`)
}
const actual = templateFiles(root)
if (existsSync(join(root, LIST_FILE))) {
const listed = new Set(parseList(readFileSync(join(root, LIST_FILE), "utf8")))
for (const path of actual) if (!listed.has(path)) found.push(`${LIST_FILE} does not list ${path}`)
for (const path of listed)
if (!actual.includes(path)) found.push(`${LIST_FILE} lists ${path}, which is not a template file`)
}
for (const path of actual) {
const text = readFileSync(join(root, path), "utf8")
if (!hasHeader(text, path)) found.push(`${path} has no "${SPDX_LINE}" header`)
}
return found
}
/** Adds every missing header and rewrites TEMPLATE-FILES; answers the paths it changed. */
export function write(root) {
const changed = []
// The list first: it is itself one of the files.
const paths = [...new Set([...templateFiles(root), LIST_FILE])].sort()
writeFileSync(join(root, LIST_FILE), listText(paths))
for (const path of paths) {
const file = join(root, path)
if (!statSync(file).isFile()) continue
const before = readFileSync(file, "utf8")
const after = withHeader(before, path)
if (after !== before) {
writeFileSync(file, after)
changed.push(path)
}
}
return changed
}
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
const root = join(dirname(fileURLToPath(import.meta.url)), "..")
const mode = process.argv[2]
if (mode === "--write") {
const changed = write(root)
console.log(
`licence: ${changed.length} file(s) changed; ${LIST_FILE} lists ${parseList(readFileSync(join(root, LIST_FILE), "utf8")).length}`,
)
} else if (mode === "--check") {
const found = problems(root)
for (const problem of found) console.error(`licence: ${problem}`)
if (found.length > 0) process.exit(1)
console.log("licence: the layout is whole")
} else {
console.error("usage: node scripts/licence.mjs --check | --write")
process.exit(2)
}
}