#!/usr/bin/env node import { readFileSync, existsSync } from 'node:fs'; const checks = []; const fail = (message) => checks.push({ ok: false, message }); const pass = (message) => checks.push({ ok: true, message }); const read = (path) => readFileSync(path, 'utf8'); const boundaryPath = 'src/shared/components/RuntimeErrorBoundary.tsx'; if (existsSync(boundaryPath)) { const boundary = read(boundaryPath); boundary.includes('componentDidCatch') ? pass('RuntimeErrorBoundary catches render crashes') : fail('RuntimeErrorBoundary must implement componentDidCatch'); boundary.includes('Frontend runtime guard') ? pass('RuntimeErrorBoundary renders user-facing fallback') : fail('RuntimeErrorBoundary needs visible fallback copy'); boundary.includes('Copy diagnostics') && boundary.includes('navigator.clipboard.writeText') ? pass('RuntimeErrorBoundary can copy diagnostics') : fail('RuntimeErrorBoundary must support one-click diagnostic copy'); } else { fail('RuntimeErrorBoundary component is missing'); } const app = read('src/App.tsx'); app.includes('') ? pass('App wraps routes in RuntimeErrorBoundary') : fail('App must wrap routes in RuntimeErrorBoundary'); const routes = read('src/app/routes.tsx'); const projectBrainClient = read('src/shared/api/projectBrainClient.ts'); projectBrainClient.includes('const useRealClient = false') ? pass('Synchronous projectBrainClient cannot call async realClient') : fail('projectBrainClient must not route sync selectors to async realClient'); projectBrainClient.includes('Real HTTP calls are async') ? pass('Sync/real client boundary is documented') : fail('Sync/real client boundary must be documented'); const projectContext = read('src/shared/contexts/ProjectContext.tsx'); projectContext.includes('toProjectArray(getProjects())') ? pass('ProjectContext normalizes project arrays') : fail('ProjectContext must normalize getProjects result before .map'); projectContext.includes('toFeatureGroups(project)') ? pass('ProjectContext guards featureGroups access') : fail('ProjectContext must guard featureGroups access'); projectContext.includes('realClient.projects.listProjects()') && projectContext.includes('reloadProjectData') ? pass('ProjectContext can hydrate projects from the real backend') : fail('ProjectContext must expose a real backend project hydration path'); projectContext.includes('realClient.projects.createProject') && projectContext.includes('Project could not be saved to the backend') ? pass('ProjectContext can create projects through the real backend') : fail('ProjectContext must create projects through the real backend in real mode'); projectContext.includes('realClient.workspace.updateActiveContextPack') && projectContext.includes('Active context pack could not be saved to the backend') ? pass('ProjectContext can persist active context pack changes') : fail('ProjectContext must persist active context pack changes in real mode'); projectContext.includes('realClient.workspace.getWorkspaceState(projectId)') && projectContext.includes('realClient.projectDNA.getProjectDNA(projectId)') ? pass('ProjectContext hydrates workspace and DNA with project data') : fail('ProjectContext must hydrate workspace and DNA in real project mode'); projectContext.includes('projectDataMode: "local" | "real"') && projectContext.includes('projectDataLoading') && projectContext.includes('projectDataError') ? pass('ProjectContext exposes data mode, loading and error state') : fail('ProjectContext must expose project data mode, loading and error state'); routes.includes('ProjectRouteSync') && routes.includes('routeProjectExists') && routes.includes('setActiveProjectId(routeProjectId)') ? pass('Project routes follow known project route changes') : fail('Project route sync must follow known project route changes without activating unknown slugs'); const projectsPage = read('src/features/projects/pages/Projects.tsx'); projectsPage.includes('Array.isArray(project.featureGroups)') ? pass('Projects page guards featureGroups before stats') : fail('Projects page must guard featureGroups before stats'); projectsPage.includes('project.workspaceState?.focusAreas?.length') ? pass('Projects page guards focusAreas length') : fail('Projects page must guard focusAreas length'); projectsPage.includes('Database projects') && projectsPage.includes('Reload projects') ? pass('Projects page exposes real data mode and reload affordance') : fail('Projects page must expose real data mode and reload affordance'); const workspacePage = read('src/features/projects/pages/Workspace.tsx'); workspacePage.includes('Database-backed project context') && workspacePage.includes('Reload project') ? pass('Workspace exposes real project context and reload affordance') : fail('Workspace must expose real project context and reload affordance'); const failures = checks.filter((check) => !check.ok); if (failures.length) { console.error('ProjectBrain frontend runtime readiness: FAIL'); for (const check of checks) console.error(`${check.ok ? '✓' : '✗'} ${check.message}`); process.exit(1); } console.log('ProjectBrain frontend runtime readiness: PASS'); for (const check of checks) console.log(`✓ ${check.message}`);