import assert from 'node:assert/strict'; import {existsSync, readFileSync, readdirSync} from 'node:fs'; import {join} from 'node:path'; import {XMLValidator} from 'fast-xml-parser'; const root = process.cwd(); const read = (path) => readFileSync(join(root, path), 'utf8'); const config = read('docusaurus.config.ts'); const packageJson = JSON.parse(read('package.json')); const globalCss = read('src/css/custom.css'); const homepageCss = read('src/pages/index.module.css'); assert.match(config, /defaultMode:\s*'dark'/, 'dark must remain the default theme'); assert.match(config, /disableSwitch:\s*false/, 'theme switch must be enabled'); assert.match(globalCss, /\[data-theme=['"]light['"]\]/, 'global CSS needs an explicit light palette'); assert.match(homepageCss, /:global\(\[data-theme=['"]light['"]\]\)/, 'homepage needs a light palette'); assert.doesNotMatch(globalCss, /html,\s*body\s*\{\s*background:\s*#08101b/, 'document background must not be hard-coded dark'); for (const section of ['projects', 'teams', 'members', 'board', 'governance']) { assert.match(config, new RegExp(`id:\\s*'${section}'`), `${section} docs plugin is missing`); assert.match(config, new RegExp(`sidebarId:\\s*'${section}Sidebar'`), `${section} navbar item does not target its dedicated sidebar`); assert.ok(existsSync(join(root, `sidebars/${section}.ts`)), `${section} sidebar file is missing`); } assert.match(config, /id:\s*'ways-of-working'/, 'Ways of Working docs plugin is missing'); assert.match(config, /sidebarId:\s*'waysOfWorkingSidebar'/, 'Ways of Working navbar item does not target its dedicated sidebar'); assert.ok(existsSync(join(root, 'sidebars/ways-of-working.ts')), 'Ways of Working sidebar file is missing'); assert.ok(existsSync(join(root, 'docs/ways-of-working/index.md')), 'Ways of Working entry page is missing'); assert.ok(existsSync(join(root, 'docs/ways-of-working/onboarding.md')), 'Onboarding page is missing'); assert.match(config, /id:\s*'architecture-high-level'/, 'Architecture High Level docs plugin is missing'); assert.match(config, /sidebarId:\s*'architectureHighLevelSidebar'/, 'Architecture High Level navbar item does not target its dedicated sidebar'); assert.match(config, /label:\s*'Architecture'/, 'Architecture navbar label is missing'); assert.doesNotMatch(config, /label:\s*'Architecture High Level'/, 'legacy Architecture High Level menu label must not remain'); assert.ok(existsSync(join(root, 'sidebars/architecture-high-level.ts')), 'Architecture High Level sidebar file is missing'); const expectedLeftNavLabels = ['Projects', 'Ways of Working', 'Teams', 'Members', 'Board', 'Governance', 'Architecture']; const leftNavLabels = [...config.matchAll(/\{type:\s*'docSidebar',[^\n]+label:\s*'([^']+)',\s*position:\s*'left'\}/g)] .map((match) => match[1]); assert.deepEqual(leftNavLabels, expectedLeftNavLabels, 'Architecture must be the final left-side menu item'); const waysOfWorkingSidebar = read('sidebars/ways-of-working.ts'); assert.match(waysOfWorkingSidebar, /link:\s*\{type:\s*'doc',\s*id:\s*'index'\}/, 'Ways of Working must link to its entry page'); assert.match(waysOfWorkingSidebar, /items:\s*\[\{type:\s*'doc',\s*id:\s*'onboarding',\s*label:\s*'Onboarding'\}\]/, 'Onboarding must be the first child page'); const onboarding = read('docs/ways-of-working/onboarding.md'); for (const term of ['Your first 20 minutes', 'Use the seven channels correctly', 'Understand authority', 'Know what “done” means', 'Security and credentials', 'Onboarding complete']) { assert.ok(onboarding.includes(term), `Onboarding is missing: ${term}`); } assert.match(config, /baseUrl:\s*'\/corp-v1\/portal\/'/, 'Pages base URL must follow the renamed repository'); assert.match(config, /projectName:\s*'portal'/, 'Docusaurus project name must be portal'); assert.doesNotMatch(config, /sterring-commitee/, 'legacy repository name must not remain in Docusaurus config'); assert.doesNotMatch(config, /Pilot Model|docs\/pilot|routeBasePath:\s*'pilot'/, 'legacy Pilot Model navigation must not remain'); const board = read('docs/board/index.md'); for (const memberId of ['1518725627845283888', '476287310627864587', '773109855497093191', '364383883778850827']) { assert.ok(board.includes(memberId), `board page is missing Discord member ${memberId}`); } assert.match(board, /Only these Board members may approve a project kickoff or project closure/i); assert.match(board, /Only these Board members may approve changes to `corp-v1--main`/i); const aerosimProject = read('docs/projects/aerosim.md'); for (const marker of [ 'Delivery progress checkpoint — 2026-09-03', '4 / 114', '3.5%', '5 / 114', '4.4%', '0 / 114', 'TASK-0015', 'PR #13', '58d33ffbd3ca21ffdc5882a75de8fe71c25188b6', '29d5e7810e63f3407f62730c1b2c71bc0644310b', ]) { assert.ok(aerosimProject.includes(marker), `AeroSim progress checkpoint is missing: ${marker}`); } assert.match(aerosimProject, /Released[^\n]*0\s*\/\s*114[^\n]*0%/i, 'AeroSim must distinguish released progress'); assert.match(aerosimProject, /ToBeReleased[^\n]*4\s*\/\s*114[^\n]*3\.5%/i, 'AeroSim must expose canonical delivery completion'); assert.match(aerosimProject, /repository-verified[^\n]*5\s*\/\s*114[^\n]*4\.4%/i, 'AeroSim must expose pending-reconciliation implementation evidence'); assert.doesNotMatch(aerosimProject, /Application repository[^\n]*14b5c0e8f25215d8bb4f41edcc0cdff4a87c342c/, 'stale AeroSim application SHA must not remain'); assert.doesNotMatch(aerosimProject, /Documentation repository[^\n]*5f674525f6c8b5ce29fe2653350c6188fb97ea01/, 'stale AeroSim documentation SHA must not remain'); const lifecycle = read('docs/governance/project-lifecycle.md'); assert.match(lifecycle, /Board approval/i, 'project lifecycle must require Board approval'); assert.match(lifecycle, /central project-channel reference skill/i, 'lifecycle must govern central channel-reference skill changes'); assert.match(lifecycle, /project-adopted channel skill/i, 'lifecycle must require project skill synchronization'); for (const section of ['projects', 'teams', 'members']) { const sidebar = read(`sidebars/${section}.ts`); assert.match(sidebar, /label:\s*'Registry'/, `${section} sidebar registry label must be concise`); assert.doesNotMatch(sidebar, /label:\s*'(Projects|Teams|Members) Registry'/, `${section} sidebar must not repeat its section name`); } assert.ok(packageJson.dependencies?.['@easyops-cn/docusaurus-search-local'], 'local search dependency is missing'); assert.match(config, /@easyops-cn\/docusaurus-search-local/, 'local search theme is not configured'); assert.ok(packageJson.dependencies?.['docusaurus-plugin-drawio'], 'Draw.io plugin dependency is missing'); assert.ok(packageJson.devDependencies?.['raw-loader'], 'Draw.io raw-loader dependency is missing'); assert.match(config, /\['drawio',\s*\{lib:\s*'\/corp-v1\/portal\/drawio\/viewer\.min\.js'\}\]/, 'Draw.io plugin must use the self-hosted viewer'); assert.ok(existsSync(join(root, 'static/drawio/viewer.min.js')), 'self-hosted Draw.io viewer is missing'); const drawioLicense = read('static/drawio/LICENSE'); assert.match(drawioLicense, /MIT License/, 'vendored Draw.io viewer license is missing'); assert.match(drawioLicense, /Copyright \(c\) 2022 xiguaxigua/, 'vendored Draw.io viewer copyright notice is missing'); assert.match(config, /indexPages:\s*true/, 'custom pages must be indexed'); const searchRoutesMatch = config.match(/docsRouteBasePath:\s*\[([^\]]+)\]/); assert.ok(searchRoutesMatch, 'search route configuration is missing'); const searchRoutes = searchRoutesMatch[1]; for (const route of ['/', 'architecture-high-level', 'projects', 'teams', 'members', 'ways-of-working', 'board', 'governance']) { assert.ok(searchRoutes.includes(`'${route}'`), `search does not include route base ${route}`); } const architectureSidebar = read('sidebars/architecture-high-level.ts'); const expectedArchitectureSidebar = `import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; const sidebars: SidebarsConfig = { architectureHighLevelSidebar: [ {type: 'doc', id: 'context', label: 'Context'}, {type: 'doc', id: 'overview', label: 'Container Overview'}, {type: 'doc', id: 'operating-model', label: 'Operating Model'}, {type: 'doc', id: 'overview-channels', label: 'Overview Channels'}, {type: 'doc', id: 'overview-skills', label: 'Overview Skills'}, {type: 'doc', id: 'overview-repositories', label: 'Overview Repositories'}, ], }; export default sidebars;`; assert.equal( architectureSidebar.trim(), expectedArchitectureSidebar, 'Architecture High Level sidebar must contain exactly the six requested documents in order', ); const architecturePages = { context: read('docs/architecture-high-level/context.mdx'), overview: read('docs/architecture-high-level/overview.mdx'), operatingModel: read('docs/architecture-high-level/operating-model.mdx'), channels: read('docs/architecture-high-level/overview-channels.mdx'), skills: read('docs/architecture-high-level/overview-skills.mdx'), repositories: read('docs/architecture-high-level/overview-repositories.mdx'), }; for (const [name, page] of Object.entries(architecturePages)) { assert.match(page, /!!raw-loader!\.\/diagrams\/.+\.drawio/, `${name} page must render an editable Draw.io source`); } for (const term of ['seven channel skills', 'seven engineering skills', 'Gitea Actions', 'scheduler jobs', 'corp-v1-board', 'corp-v1-guild', 'corp-v1--guild', 'UI/UX', 'one AI agent', 'Hermes Agent', 'MicroK8s', 'Penpot', 'Argo CD', 'Harbor']) { assert.ok(Object.values(architecturePages).some((page) => page.includes(term)), `architecture explanation is missing: ${term}`); } assert.match(architecturePages.channels, /two central portfolio workspaces/i, 'channels overview must distinguish Board and Guild as central workspaces'); assert.match(architecturePages.channels, /neither is an eighth project channel/i, 'Guild must not be classified as an eighth project channel'); assert.match(architecturePages.skills, /not copied as a fifteenth project skill/i, 'Guild must remain central rather than project-adopted'); assert.doesNotMatch(architecturePages.channels, /one portfolio-governance channel/i, 'superseded single-central-workspace wording must not remain'); for (const term of ['Corp v1 Board members', 'Project leads and contributors', 'Prospective project sponsors and contributors', 'Solution users and reviewers']) { assert.ok(architecturePages.context.includes(term), `system context is missing actor: ${term}`); } for (const term of ['only two external software systems', 'LLM Provider', 'ICA or GPT Codex', 'Communication Service — Discord']) { assert.ok(architecturePages.context.includes(term), `system context is missing boundary rule: ${term}`); } for (const term of ['Gitea Actions runners', 'Penpot', 'LEGO Cloud Pages', 'Harbor', 'Argo CD', 'Identity and access', 'Ingress edge', 'Gondor v1 MicroK8s']) { assert.ok(architecturePages.overview.includes(term), `container overview is missing internal container: ${term}`); } for (const term of ['corp-v1--main', 'corp-v1-channel-ui-ux', 'development-gitops-argo-cd', 'drawio-main', 'systematic-debugging', 'gondor-v1-nodes']) { assert.ok(architecturePages.skills.includes(term), `skills overview is missing: ${term}`); } for (const [name, page] of Object.entries(architecturePages)) { assert.doesNotMatch(page, /exactly six|six project channels|six channel skills|thirteen adopted skill/i, `${name} retains the superseded six-channel model`); } for (const [name, markers] of Object.entries({ 'context.drawio': ['CORP V1 — HOME-LAB SYSTEM CONTEXT', 'HUMAN ACTORS', 'TARGET SYSTEM', 'EXTERNAL SYSTEMS · ONLY TWO', 'DISCORD', 'ICA OR GPT CODEX'], 'architecture-overview.drawio': ['CORP V1 — HOME-LAB CONTAINER OVERVIEW', 'EXTERNAL · DISCORD', 'EXTERNAL · LLM PROVIDER', 'INTERNAL CONTAINERS', 'GITEA ACTIONS RUNNERS', 'HARBOR', 'ARGO CD', 'GONDOR MICROK8S'], 'operating-model.drawio': ['CORP V1 — OPERATING MODEL FLOW', 'CORP-V1-BOARD', 'BOARD APPROVAL', 'UI/UX', 'KANBAN / FOCUS', 'VERIFY & RECORD'], 'channels.drawio': ['CORP V1 — BOARD, GUILD & PROJECT CHANNELS', 'CORP-V1-BOARD', 'CORP-V1-GUILD', 'GENERAL', 'ARCHITECTURE', 'UI/UX', 'RELEASES', 'MULTIPLE FOCUSED SESSIONS'], 'skills.drawio': ['CORP V1 — SKILLS MODEL', 'CENTRAL SOURCES', 'CORP-V1--GUILD', 'DELIBERATE ADOPTION', 'PROJECT COPIES', 'FOCUSED SESSION'], 'repositories.drawio': ['CORP V1 — ORGANIZATIONS, REPOSITORIES & DELIVERY', 'PROJECT ORGANIZATION', 'SKILLS ORGANIZATION', '7 adopted channel-skill repos', 'GITEA ACTIONS'], })) { const diagram = read(`docs/architecture-high-level/diagrams/${name}`); const xmlResult = XMLValidator.validate(diagram); assert.equal(xmlResult, true, `${name} is malformed XML: ${JSON.stringify(xmlResult)}`); assert.match(diagram, /]/, `${name} is not a Draw.io source file`); for (const marker of markers) assert.ok(diagram.includes(marker), `${name} is missing: ${marker}`); assert.doesNotMatch(diagram, /Six purpose-specific|6 channel skills|6 adopted channel-skill repos/i, `${name} retains the superseded six-channel model`); } if (existsSync(join(root, 'build'))) { const filename = readdirSync(join(root, 'build')).find((name) => /^search-index(?:-[a-f0-9]+)?\.json$/.test(name)); assert.ok(filename, 'production search index is missing'); const search = readFileSync(join(root, 'build', filename), 'utf8').toLowerCase(); for (const term of ['projects registry', 'teams registry', 'members registry', 'board members', 'project lifecycle', 'corp v1 board', 'ways of working', 'your first 20 minutes', 'onboarding complete', 'container overview', 'operating model', 'overview channels', 'overview skills', 'overview repositories']) { assert.ok(search.includes(term), `search index is missing: ${term}`); } } console.log('Portal behavior checks passed.');