clusterflux-public/scripts/check-docs.js
Clusterflux release 18cba9c609 Public release release-6756e5208c8b
Source commit: 6756e5208c8b79e83d55610251430bc1baef53a3

Public tree identity: sha256:2f58837c3759b4f466cbc274571ee71bc0d24c7552dc009c186394e99123da87
2026-07-17 04:13:46 +02:00

111 lines
4 KiB
JavaScript

#!/usr/bin/env node
const fs = require("node:fs");
const path = require("node:path");
const root = path.resolve(__dirname, "..");
const publicDocs = [
"README.md",
"SECURITY.md",
"docs/getting-started.md",
"docs/architecture.md",
"docs/nodes.md",
"docs/environments.md",
"docs/artifacts.md",
"docs/debugging.md",
"docs/task-abi.md",
"docs/self-hosting.md",
"docs/security.md",
];
const privateDocs = [
"private/docs/hosted-deployment.md",
"private/docs/authentik.md",
"private/docs/community-policy.md",
"private/docs/bandwidth-and-cost-controls.md",
"private/docs/publishing.md",
];
const internalDocs = ["internal/finish_mvp_2.md"];
const filteredPublicTree =
process.env.CLUSTERFLUX_FILTERED_PUBLIC_TREE === "1" ||
fs.existsSync(path.join(root, "CLUSTERFLUX_PUBLIC_TREE.json"));
const failures = [];
const expectExactMarkdownSet = (directory, expected) => {
const actual = fs
.readdirSync(path.join(root, directory), { withFileTypes: true })
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
.map((entry) => path.posix.join(directory, entry.name))
.sort();
const wanted = [...expected].sort();
if (JSON.stringify(actual) !== JSON.stringify(wanted)) {
failures.push(`${directory} markdown set is ${actual.join(", ")}; expected ${wanted.join(", ")}`);
}
};
const requiredDocs = filteredPublicTree
? publicDocs
: [...publicDocs, ...privateDocs, ...internalDocs];
for (const file of requiredDocs) {
if (!fs.existsSync(path.join(root, file))) failures.push(`missing canonical document: ${file}`);
}
expectExactMarkdownSet("docs", publicDocs.filter((file) => file.startsWith("docs/")));
if (filteredPublicTree) {
for (const directory of ["private", "internal"]) {
if (fs.existsSync(path.join(root, directory))) {
failures.push(`${directory}/ must not exist in the filtered public tree`);
}
}
}
const forbidden = [
[/\bmvp\b/i, "internal milestone term"],
[/acceptance criteria/i, "internal gate language"],
[/release verification/i, "internal release language"],
[/public\/private source split/i, "source split narrative"],
[/founder|business decisions/i, "business planning narrative"],
[/hacker news|\bHN\b/, "launch-channel narrative"],
[/\busers can\b/i, "indirect reader wording"],
[/node\s+scripts\//i, "developer script instruction"],
[/scripts\/[^\s)]*smoke/i, "smoke script instruction"],
[/internal\/[^\s)]*/i, "internal tooling reference"],
[/private\/[^\s)]*/i, "private source reference"],
];
const topLevelCommands = new Set([
"doctor", "login", "logout", "auth", "agent", "key", "project", "inspect",
"build", "bundle", "run", "node", "process", "task", "logs", "artifact",
"dap", "debug", "quota", "admin",
]);
for (const file of publicDocs) {
const absolute = path.join(root, file);
const content = fs.readFileSync(absolute, "utf8");
for (const [pattern, description] of forbidden) {
if (pattern.test(content)) failures.push(`${file}: contains ${description}`);
}
for (const match of content.matchAll(/\]\(([^)#]+)(?:#[^)]+)?\)/g)) {
const target = match[1];
if (/^(?:https?:|mailto:)/.test(target)) continue;
const resolved = path.resolve(path.dirname(absolute), target);
if (!fs.existsSync(resolved)) failures.push(`${file}: broken link ${target}`);
}
for (const line of content.split(/\r?\n/)) {
const command = line.trim().match(/^clusterflux\s+([a-z][a-z-]*)\b/);
if (command && !topLevelCommands.has(command[1])) {
failures.push(`${file}: unknown top-level CLI command ${command[1]}`);
}
}
}
const rootMarkdown = fs
.readdirSync(root, { withFileTypes: true })
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
.map((entry) => entry.name)
.sort();
if (JSON.stringify(rootMarkdown) !== JSON.stringify(["README.md", "SECURITY.md"])) {
failures.push(`top-level markdown set is ${rootMarkdown.join(", ")}; expected README.md, SECURITY.md`);
}
if (failures.length) {
for (const failure of failures) console.error(failure);
process.exit(1);
}
console.log("documentation checks passed");