/** * The README quality gate, as pure functions of the markdown. * * These are a LINE-FOR-LINE port of TREK-Plugins' scripts/check-readme.mjs. They live apart * from the checks that call them because the same gate runs twice, against two different * READMEs, and both must agree with CI: * * - offline, against README.md in the WORKING TREE (`status` / `validate`) * - over the network, against README.md at the PINNED COMMIT (`preflight`) * * The two differ more often than you would think — an author fills the README in and forgets * to commit it, and the tree is green while the tag CI grades is not. Keeping one implementation * means the offline pass can never be more lenient than the network one; only more current. * * If you change a rule here, change scripts/check-readme.mjs to match, or you have built a * false green. test/checks-parity.test.ts asserts the two agree. */ /** Every section CI demands, matched case-insensitively as a substring of a heading. */ export const REQUIRED_SECTIONS = ['What it does', 'Screenshots', 'Permissions', 'Setup']; /** Template prose that means the author never edited the scaffold. */ export const PLACEHOLDER_PATTERNS = [ /\{\{[^}]*\}\}/, /\bREPLACE_ME\b/i, /\bDescribe (what|the)\b/i, /\byour-name\/trek-plugin/i, ]; export const MIN_PROSE_CHARS = 400; /** * The heading body, with the trailing HTML comment authors use to hide anchors cut off. * * Cut with indexOf rather than a regex on purpose. The obvious spellings — the * `(.+?)\s*(?:/g, '') .replace(/```[\s\S]*?```/g, '') .replace(/!\[[^\]]*\]\([^)]*\)/g, '') .replace(/\[[^\]]*\]\([^)]*\)/g, '') .replace(/^#{1,6}\s+.*$/gm, '') .replace(/^\s*\|.*$/gm, '') .replace(/[#>*_`|-]/g, '') .replace(/\s+/g, ' ') .trim().length; } /** Manifest permissions that are never named anywhere in the README. CI rejects these. */ export function undocumentedPermissions(md: string, permissions: string[]): string[] { const lower = md.toLowerCase(); return permissions.filter((p) => !lower.includes(p.toLowerCase())); }