/** * Doc-sync gate for package-group subsystem references. Every package group * either links at least one existing `docs/subsystems/` page from its English * group README or carries an explicit, justified exemption below. */ import { existsSync, globSync, readFileSync } from 'node:fs' import { resolve, sep } from 'node:path' import { parseMarkdown, visitMarkdown } from './markdown.ts' const root = resolve(import.meta.dirname, '..') /** * Package groups that do not own a standalone subsystem reference. Reasons * are reviewable policy: a new group cannot silently inherit an exemption. */ export const GROUPS_WITHOUT_SUBSYSTEM_PAGE: Readonly> = { acp: 'Protocol transport entry point; the server package README owns its interoperability contract.', boot: 'Shared application-bin boot library rather than a runtime subsystem.', bundle: 'Composition patch carriers whose mounted packages own all runtime contracts.', examples: 'Non-product demonstration compositions whose mounted packages own all runtime contracts.', hooks: 'External hook-protocol bridges over existing interception points, not a new Harness service.', sdk: 'Out-of-process protocol and client packages whose package READMEs own the SDK contracts.', util: 'Low-level primitives whose business semantics remain with their consuming subsystems.', } /** Result of auditing package-group subsystem documentation. */ export interface SubsystemPageAudit { /** Package groups discovered from group READMEs or child package manifests. */ readonly groups: number /** Groups carrying at least one direct subsystem-page link. */ readonly linked: number /** Groups covered by an explicit no-page policy. */ readonly exempt: number /** Actionable contract violations. */ readonly violations: readonly string[] } /** Normalize one filesystem glob result to repository slash form. */ function normalize(path: string): string { return path.split(sep).join('/') } /** Extract the package-group segment from a repository-relative path. */ function groupOf(path: string): string { const group = path.split('/')[1] if (group === undefined || group.length === 0) throw new Error(`invalid package path: ${path}`) return group } /** Return canonical subsystem-page targets linked by one group README. */ function subsystemLinks(source: string): string[] { const links = new Set() visitMarkdown(parseMarkdown(source), (node) => { if (node.type !== 'link') return const match = /^\.\.\/\.\.\/docs\/subsystems\/([^/#?]+\.md)(?:#[^?#]*)?$/.exec(node.url) const page = match?.[1] if (page !== undefined && page !== 'README.md' && !page.endsWith('.zh.md')) links.add(`docs/subsystems/${page}`) }) return [...links].sort() } /** * Audit package-group subsystem ownership for one repository tree. * @param scanRoot - repository root containing `packages/` and `docs/`. * @param exemptions - groups intentionally carrying no subsystem-page link. * @returns counts plus every actionable violation. */ export function auditSubsystemPages( scanRoot: string = root, exemptions: Readonly> = GROUPS_WITHOUT_SUBSYSTEM_PAGE, ): SubsystemPageAudit { const readmes = globSync('packages/*/README.md', { cwd: scanRoot }).map(normalize).sort() const manifests = globSync('packages/*/*/package.json', { cwd: scanRoot }).map(normalize).sort() const groups = new Set([...readmes, ...manifests].map(groupOf)) const violations: string[] = [] let linked = 0 let exempt = 0 for (const [group, reason] of Object.entries(exemptions)) { if (!groups.has(group)) { violations.push(`exemption ${group}: no matching package group; remove the stale entry`) } if (reason.trim().length === 0) { violations.push(`exemption ${group}: missing justification for omitting a subsystem page`) } } for (const group of [...groups].sort()) { const readme = `packages/${group}/README.md` const readmePath = resolve(scanRoot, readme) if (!existsSync(readmePath)) { violations.push(`${readme}: package group has no group README declaring subsystem ownership`) continue } const links = subsystemLinks(readFileSync(readmePath, 'utf8')) const isExempt = Object.hasOwn(exemptions, group) if (links.length === 0) { if (isExempt) { exempt += 1 } else { violations.push( `${readme}: no reader-visible direct docs/subsystems/*.md link; add the owning page and link,` + ' or add a justified GROUPS_WITHOUT_SUBSYSTEM_PAGE entry', ) } continue } linked += 1 if (isExempt) { violations.push(`${readme}: links a subsystem page but remains exempt; remove the stale exemption`) } for (const page of links) { if (!existsSync(resolve(scanRoot, page))) { violations.push(`${readme}: linked subsystem page does not exist: ${page}`) } } } return { groups: groups.size, linked, exempt, violations } } /** Run the repository audit as a standalone doc-sync gate. */ function main(): void { const audit = auditSubsystemPages() if (audit.violations.length > 0) { console.error('verify-subsystem-pages: package-group documentation violations found:') for (const violation of audit.violations) console.error(` ${violation}`) process.exit(1) } console.log( `verify-subsystem-pages: ${String(audit.groups)} group(s) checked` + ` (${String(audit.linked)} linked, ${String(audit.exempt)} explicitly exempt), all conform.`, ) } if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) main()