68 lines
1.8 KiB
JavaScript
68 lines
1.8 KiB
JavaScript
import { access } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import {
|
|
readTextFileIfSmall,
|
|
relative,
|
|
repositoryRoot,
|
|
walkFiles,
|
|
} from "./repository-files.mjs";
|
|
|
|
const requiredDocuments = [
|
|
"AGENTS.md",
|
|
"docs/README.md",
|
|
"docs/agent-playbook.md",
|
|
"docs/implementation-plan.md",
|
|
"docs/quality-gates.md",
|
|
"docs/decisions.md",
|
|
];
|
|
|
|
const failures = [];
|
|
|
|
for (const required of requiredDocuments) {
|
|
try {
|
|
await access(path.join(repositoryRoot, required));
|
|
} catch {
|
|
failures.push(`missing required document: ${required}`);
|
|
}
|
|
}
|
|
|
|
const markdownFiles = (await walkFiles()).filter((file) => file.endsWith(".md"));
|
|
const markdownLink = /!?\[[^\]]*\]\(([^)]+)\)/g;
|
|
|
|
for (const markdownFile of markdownFiles) {
|
|
const content = await readTextFileIfSmall(markdownFile);
|
|
if (content === null) continue;
|
|
|
|
for (const match of content.matchAll(markdownLink)) {
|
|
let target = match[1].trim();
|
|
if (target.startsWith("<") && target.endsWith(">")) {
|
|
target = target.slice(1, -1);
|
|
}
|
|
target = target.split(/\s+["']/u, 1)[0];
|
|
if (
|
|
target === "" ||
|
|
target.startsWith("#") ||
|
|
/^[a-z][a-z\d+.-]*:/iu.test(target)
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
const pathPart = target.split("#", 1)[0].split("?", 1)[0];
|
|
const decodedPath = decodeURIComponent(pathPart);
|
|
const resolved = path.resolve(path.dirname(markdownFile), decodedPath);
|
|
try {
|
|
await access(resolved);
|
|
} catch {
|
|
failures.push(`${relative(markdownFile)} -> ${target}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error("Documentation verification failed:");
|
|
for (const failure of failures) console.error(`- ${failure}`);
|
|
process.exitCode = 1;
|
|
} else {
|
|
console.log(`Documentation verification passed (${markdownFiles.length} Markdown files).`);
|
|
}
|