(options = {})
| 814 | writeText(dashboardPath, renderAppServerDashboard(summary, { source: options.source })); |
| 815 | return { |
| 816 | projectRoot, |
| 817 | summaryPath, |
| 818 | dashboardPath, |
| 819 | summary, |
| 820 | }; |
| 821 | } |
| 822 | |
| 823 | function checkCodexReadiness(options = {}) { |
| 824 | const projectRoot = path.resolve(options.projectRoot || process.cwd()); |
| 825 | const checks = []; |
| 826 | const add = (id, pass, detail, improvement) => checks.push({ id, pass: Boolean(pass), detail, improvement }); |
| 827 | |
| 828 | const manifestPath = path.join(projectRoot, '.codex-plugin', 'plugin.json'); |
| 829 | const manifest = readJsonIfExists(manifestPath, { allowLineComment: true }); |
| 830 | add('plugin-manifest', Boolean(manifest), manifestPath, 'Codex can discover Citadel as a plugin instead of ad hoc prompts.'); |
| 831 | if (manifest) { |
| 832 | const skillsPath = manifest.skills ? path.resolve(projectRoot, manifest.skills) : null; |
| 833 | const hooksPath = manifest.hooks ? path.resolve(projectRoot, manifest.hooks) : null; |
| 834 | const mcpPath = manifest.mcpServers ? path.resolve(projectRoot, manifest.mcpServers) : null; |
| 835 | add('plugin-description', !/claude/i.test(manifest.description || '') && /codex/i.test(`${manifest.description || ''} ${manifest.interface?.shortDescription || ''}`), manifest.description, 'Prevents Codex users from seeing stale Claude-specific packaging.'); |
| 836 | add('plugin-skills-path', skillsPath && fs.existsSync(skillsPath), skillsPath || 'missing', 'Skills are loaded from a real plugin path.'); |
| 837 | add('plugin-hooks-path', hooksPath && fs.existsSync(hooksPath), hooksPath || 'missing', 'Lifecycle safety hooks are bundled with the plugin.'); |
| 838 | add('plugin-mcp-path', mcpPath && fs.existsSync(mcpPath), mcpPath || 'missing', 'Codex can load Citadel state through MCP.'); |
| 839 | } |
| 840 | |
| 841 | const configPath = path.join(projectRoot, '.codex', 'config.toml'); |
| 842 | const config = readTextIfExists(configPath); |
| 843 | add('codex-config', Boolean(config), configPath, 'Project installs have Codex feature flags and MCP wiring.'); |
| 844 | add('feature-hooks', /\bhooks\s*=\s*true\b/.test(config) && !/\bcodex_hooks\s*=\s*true\b/.test(config), 'canonical hooks feature', 'Uses the current hooks feature key and rejects deprecated output.'); |
| 845 | add('mcp-citadel-state', /\[mcp_servers\.citadel-state\]/.test(config), 'citadel-state MCP config', 'Codex can query planning and verification state directly.'); |
| 846 | |
| 847 | const agentsDir = path.join(projectRoot, '.codex', 'agents'); |
| 848 | const agentCount = fs.existsSync(agentsDir) |
| 849 | ? fs.readdirSync(agentsDir).filter((name) => name.endsWith('.toml')).length |
| 850 | : 0; |
| 851 | add('codex-agents', agentCount > 0, `${agentCount} agent manifest(s)`, 'Citadel agents can map to native Codex subagents.'); |
| 852 | |
| 853 | const agentsMd = path.join(projectRoot, 'AGENTS.md'); |
| 854 | const agentsText = readTextIfExists(agentsMd); |
| 855 | add('agents-guidance', Boolean(agentsText), agentsMd, 'Codex gets project and review guidance before work starts.'); |
| 856 | add('review-guidelines', /review guidelines/i.test(agentsText), 'Review guidelines section', 'Codex GitHub review can follow repository-specific review rules.'); |
| 857 | |
| 858 | const artifacts = verifyAppArtifacts({ projectRoot, requireExistingPaths: false }); |
| 859 | add('artifact-manifest-readable', artifacts.pass, `${artifacts.count} artifact record(s)`, 'Codex app evidence can be discovered after QA and live-preview runs.'); |
| 860 | |
| 861 | const report = { |
| 862 | projectRoot, |
| 863 | checkedAt: nowIso(options), |
| 864 | checks, |
| 865 | pass: checks.every((check) => check.pass), |
| 866 | }; |
| 867 | |
| 868 | if (options.write) { |
no test coverage detected