| 112 | |
| 113 | |
| 114 | def validate_codex(report: Report) -> None: |
| 115 | root = WORKTREE / ".codex" |
| 116 | if not root.is_dir(): |
| 117 | return # nothing generated yet |
| 118 | |
| 119 | # 1. Every agent .toml parses and has required fields. |
| 120 | required_agent_fields = {"name", "description", "developer_instructions"} |
| 121 | for toml_path in (root / "agents").glob("*.toml") if (root / "agents").is_dir() else []: |
| 122 | try: |
| 123 | data = tomllib.loads(toml_path.read_text(encoding="utf-8")) |
| 124 | except tomllib.TOMLDecodeError as e: |
| 125 | report.add( |
| 126 | severity="error", |
| 127 | harness="codex", |
| 128 | path=toml_path, |
| 129 | message=f"TOML parse error: {e}", |
| 130 | remediation="Regenerate via `make generate HARNESS=codex PLUGIN=<name>`.", |
| 131 | ) |
| 132 | continue |
| 133 | missing = required_agent_fields - set(data.keys()) |
| 134 | if missing: |
| 135 | report.add( |
| 136 | severity="error", |
| 137 | harness="codex", |
| 138 | path=toml_path, |
| 139 | message=f"missing required TOML fields: {sorted(missing)}", |
| 140 | remediation="The source agent markdown is missing fields. Check `description:` in frontmatter.", |
| 141 | ) |
| 142 | if "sandbox_mode" in data and data["sandbox_mode"] not in { |
| 143 | "read-only", |
| 144 | "workspace-write", |
| 145 | "danger-full-access", |
| 146 | }: |
| 147 | report.add( |
| 148 | severity="error", |
| 149 | harness="codex", |
| 150 | path=toml_path, |
| 151 | message=f"invalid sandbox_mode: {data['sandbox_mode']!r}", |
| 152 | remediation="Must be one of: read-only, workspace-write, danger-full-access.", |
| 153 | ) |
| 154 | |
| 155 | # 2. Every skill SKILL.md has valid frontmatter + name matches directory. |
| 156 | skills_dir = root / "skills" |
| 157 | if skills_dir.is_dir(): |
| 158 | for skill_md in skills_dir.glob("*/SKILL.md"): |
| 159 | content = skill_md.read_text(encoding="utf-8") |
| 160 | fm, body = parse_frontmatter(content) |
| 161 | if not fm: |
| 162 | report.add( |
| 163 | severity="error", |
| 164 | harness="codex", |
| 165 | path=skill_md, |
| 166 | message="missing or invalid frontmatter", |
| 167 | remediation="SKILL.md must start with `---\\nname: ...\\ndescription: ...\\n---`.", |
| 168 | ) |
| 169 | continue |
| 170 | if fm.get("name") != skill_md.parent.name: |
| 171 | report.add( |