# Scope: Codex Support

**Created by:** Ana
**Date:** 2026-05-30

## Intent

Anatomia's pipeline cannot run on Codex. A developer who uses Codex instead of Claude Code gets scan intelligence and AGENTS.md, but not the pipeline (Think, Plan, Build, Verify). The pipeline is the product. Without it, Anatomia is a linter with opinions.

Scope 1 (Platform-Aware CLI) shipped the foundation: `platforms` config field, `platform.ts` helper module, all hardcoded `.claude/` path references replaced with config-driven calls, all display strings migrated from `claude --agent` to `ana run`, and `ana run` as a CC passthrough. Every path goes through helpers. Every invocation string goes through `agentCommand()`.

This scope builds on that foundation to deliver: Codex agent templates and manifests generated by `ana init`, a unified skill architecture with `.ana/skills/` as the canonical location and symlinks for both platforms, CC template migration to `ana run` syntax, platform-aware path helpers, `ana run` Codex dispatch with `danger-full-access` sandbox, and the `--platforms` flag on `ana init`.

After this ships, a developer runs `ana init --platforms claude,codex` and gets delivery files for both platforms. `ana run build` dispatches to whichever platform is active. The pipeline runs identically on both.

## Complexity Assessment
- **Kind:** feature
- **Size:** large — ~8 modified source files, ~18 new template files, 2 specs
- **Surface:** cli
- **Files affected:** `platform.ts`, `run.ts`, `init/assets.ts`, `init/skills.ts`, `init/index.ts`, `init/state.ts`, `check.ts`, `proof.ts`, `agents.ts`, `index.ts`, `constants.ts` (if needed), plus ~18 new files under `templates/.codex/agents/` and `templates/CLAUDE.md`
- **Blast radius:** Init orchestration refactored to be platform-conditional. `getSkillsDir()` and `getSkillsDirRel()` change return values from `.claude/skills` to `.ana/skills` (affects `check.ts`, `proof.ts`, `agents.ts`, `state.ts`). `getAgentsDir()` becomes platform-aware. `scaffoldAndSeedSkills()` target changes from `.claude/skills/` to `.ana/skills/`. Existing user re-init migrates real `.claude/skills/` dir to `.ana/skills/` + symlink. CC template agent prompts get `claude --agent` → `ana run` migration (6 files + CLAUDE.md template). New Codex templates (5 `.md` + 5 `.agent.toml`). `ana run` gains platform resolution chain and Codex spawn mechanics.
- **Estimated effort:** 2 pipeline runs (Spec 1 + Spec 2)
- **Multi-phase:** yes — two specs with a natural seam between init infrastructure and runtime dispatch

## Approach

Split into two specs along the init/runtime boundary.

**Spec 1 (Init infrastructure):** Make skills canonical in `.ana/skills/` with symlinks for both platforms. Migrate existing users' `.claude/skills/` real directories to `.ana/skills/` + symlink on re-init. Make `getSkillsDir()` return `.ana/skills` and `getAgentsDir()` platform-aware. Refactor init orchestration to be platform-conditional — extract `generateAgentsMd()` calls from `createClaudeConfiguration()`, gate `.claude/` creation on `platforms.includes('claude')`. Add `createCodexConfiguration()` as a parallel function. Create Codex templates (5 agent `.md` files + 5 `.agent.toml` manifests — no Learn). Add `--platforms` flag to `ana init` with first-init auto-detection from PATH. Migrate CC templates from `claude --agent` to `ana run` syntax (34 replacements across 6 agent files + CLAUDE.md template). Resolve the `init-flow.test.ts:157` residual and `check.ts:1411` display string residual from Scope 1.

**Spec 2 (Runtime dispatch):** Add `--platform` flag to `ana run`. Implement platform resolution chain (`--platform` flag → `ANA_PLATFORM` env → sole platform in `ana.json` → PATH detection → error). Add Codex dispatch: read `.agent.toml` manifest, construct `codex --model --sandbox -c "developer_instructions=$(cat ...)" exec` invocation with `shell: true`. Handle interactive mode for Think/Setup. Fix `getPlatformFlags()` to accept a platform parameter instead of hardcoding `platforms[0]`. Add progress output for non-interactive Codex mode. Handle deferred agents (Learn on Codex → helpful error).

The seam is clean: Spec 1 generates the files and establishes the architecture, Spec 2 runs them.

## Acceptance Criteria

- AC1: `ana init --platforms claude,codex` generates `.claude/`, `.codex/`, `.ana/skills/`, `.claude/skills` symlink, and `.agents/skills` symlink
- AC2: `ana init --platforms codex` generates `.codex/` and `.agents/skills/` symlink without generating `.claude/` or CLAUDE.md
- AC3: `.codex/agents/ana-build.md` contains the Build agent prompt with `ana run` syntax, `.agents/skills/` paths, worktree path guidance without CC tool names, and NO YAML frontmatter
- AC4: `.codex/agents/ana-build.agent.toml` contains `model = "gpt-5.5"`, `sandbox_mode = "danger-full-access"`, `model_reasoning_effort = "high"`, `mode = "exec"`
- AC5: `.ana/skills/coding-standards/SKILL.md` has unified frontmatter (`name:` + `description:`). Both `.claude/skills/coding-standards/SKILL.md` and `.agents/skills/coding-standards/SKILL.md` resolve to this same file via symlinks.
- AC6: `ana run build` on a Codex-configured project launches `codex exec` with correct `developer_instructions`, model, and `--sandbox danger-full-access` read from the manifest
- AC7: `ana run` (no argument) on Codex opens the interactive TUI with Ana's instructions loaded
- AC8: Codex Build agent calls `ana artifact save build-report {slug}` during its session and the commit succeeds (`danger-full-access` provides full git access)
- AC9: `ana init commit` stages `.codex/` and `.agents/` symlink pointers alongside `.ana/skills/` content
- AC10: Re-init with existing `.codex/agents/` preserves user customizations (merge-not-overwrite)
- AC11: Re-init with existing `.claude/skills/` real directory migrates content to `.ana/skills/` and replaces the real directory with a symlink
- AC12: `ana run build --platform codex` works when both tools are installed, reads flags from `platformFlags.codex`
- AC13: `ana run learn` on Codex shows: "The Learn agent is not yet available on Codex" with guidance to use CC
- AC14: CC agent templates (`templates/.claude/agents/*.md`) use `ana run` syntax, not `claude --agent`
- AC15: CLAUDE.md template (`templates/CLAUDE.md`) uses `ana run` syntax
- AC16: `getSkillsDir()` returns `.ana/skills` path (canonical location, works for all platforms)
- AC17: `getAgentsDir()` returns `.codex/agents` when active platform is codex
- AC18: `ana init` with no `--platforms` flag on first init auto-detects from PATH. Re-init preserves `platforms` from existing `ana.json`.
- AC19: Codex-only project (`platforms: ["codex"]`): `ana agents`, `ana setup check`, and `ana proof promote` all resolve paths correctly (no `.claude/` dependency)
- AC20: All existing tests pass. Test count does not decrease. CC behavior unchanged.

## Edge Cases & Risks

**Edge cases:**
1. Codex not in PATH but `platforms` includes codex: `ana run build` detects codex not in PATH, shows install link.
2. `.codex/agents/` exists but `ana.json` doesn't list codex: files are orphaned but harmless. `ana run` won't dispatch to Codex.
3. Both tools in PATH, multiple platforms in `ana.json`, no `--platform` flag: `ana run` reads sole platform from `ana.json` if only one entry. If multiple: error with guidance to set `--platform` or `ANA_PLATFORM`.
4. `$(cat)` with path containing spaces: agent prompt path must be quoted in the shell command. Template literal with explicit quoting.
5. Mixed team (some CC, some Codex): both sets of delivery files exist. Each developer uses `--platform` or `ANA_PLATFORM`. `ana.json` `platforms` array tracks which platforms have delivery files.
6. Skill migration conflict — both `.claude/skills/` and `.ana/skills/` have content for the same skill: use the one with more recent mtime. Don't silently overwrite.
7. Empty `platforms` array in `ana.json`: schema `.catch(['claude'])` handles parse failure, but a valid empty array passes through. `ana run` treats no platforms as an error with guidance.
8. Developer switches from CC to Codex: edit `ana.json` platforms, re-run `ana init`. Codex delivery files generated. CC files remain (harmless).
9. Codex agent crashes mid-build: no `build_report.md` written. Work item stays at `ready-for-build`. Developer re-runs.
10. AGENTS.md for Codex-only: generated regardless of platform (cross-tool standard). CLAUDE.md only generated when platforms includes `claude`.

**Risks:**
- **Skill migration data safety (MEDIUM).** Moving enriched content from `.claude/skills/` to `.ana/skills/` on re-init is a destructive operation on the real directory. Mitigated by: migration runs after atomic swap succeeds (`.ana/skills/` has the content before `.claude/skills/` is removed), mtime comparison when both locations have content, and the existing content is preserved in `.ana/skills/` even if symlink creation fails.
- **Template drift at N=2 (MEDIUM).** 6 agent templates × 2 platforms = 12 files. When we update an agent prompt, we update it in two template directories. Grep catches misses. Accepted tradeoff — canonical extraction is premature at N=2.
- **`shell: true` in spawnSync (LOW).** Required for `$(cat)` expansion. The agent prompt path is constructed by our CLI from `ana.json` + project root — not user input. No injection vector.
- **`getSkillsDir()` return value change (LOW).** Changes from `.claude/skills` to `.ana/skills`. All consumers use the helper, so the change propagates automatically. Existing proof chain entries with `.claude/skills/` paths still resolve through symlinks — proof.ts never does string equality comparison on `promoted_to` paths.
- **CC `/skills` slash command autocomplete (LOW).** May not list symlinked skills (CC issue #14836). Skills still load and execute via frontmatter. UI-only limitation.

## Rejected Approaches

**Canonical template extraction to `.ana/agents/` with derivation layer.** Build a template variable system (`{{invoke:ana-plan}}`, `{{skill-path}}`) and extract shared prompt bodies into `.ana/agents/`, deriving CC and Codex versions at init time. Premature at N=2. We have 12 template files, not 120. The DRY violation costs a grep when updating prompts. The extraction costs a variable resolution system, a canonical-vs-derived distinction, and a new failure surface. Build when N=3 arrives.

**Platform abstraction framework (`Platform { init(), run(), commitRoots() }`).** Interface-based platform module system. Premature at N=2. Two parallel functions (`createClaudeConfiguration` / `createCodexConfiguration`) and platform-aware helpers serve the same purpose without the framework overhead.

**`workspace-write` sandbox with post-exit commit architecture.** Three scrutiny agents discovered `.git` is hardcoded read-only in `workspace-write`, breaking `git commit` for every agent. The proposed workaround (agent writes files, wrapper commits after exit) added massive complexity: worktree lifecycle changes, code staging logic, multi-phase detection, Think agent degradation. Eliminated by using `danger-full-access` — same terminal access model as Claude Code. The flag name is OpenAI branding, not a threat assessment.

**Agents SDK orchestrator.** Python runtime dependency. Wrong shape for interactive mode. Overengineered for the isolation we already have. New failure surface.

**Generate Learn template for Codex with disclaimer note.** The Learn agent's diagnostic model (curation gaps, skill frontmatter reasoning) is fundamentally CC-specific. Generating a template with wrong reasoning is worse than no template — it creates scaffolding we tear down and gives wrong advice if someone runs it. Cleaner to show a helpful error and defer to V1.1.

**Skill migration deferred to future scope.** If Scope 2 changes `scaffoldAndSeedSkills()` to write to `.ana/skills/` but doesn't migrate existing users' `.claude/skills/` real directories, an existing CC user who adds Codex via `--platforms claude,codex` gets split-brain skills: CC reads from `.claude/skills/` (enriched), Codex reads from `.agents/skills/ → .ana/skills/` (unenriched templates). That defeats the symlink architecture. Migration must happen in Scope 2.

## Open Questions

1. **Setup agent on Codex: stretch or committed?** The Codex Setup template is generated (line 655 rewrite is trivial). But Setup is interactive and benefits from CC's conversation features (memory, slash commands). Should the scope commit to Setup working on Codex, or mark it as "generated but CC-recommended"? Lean: generated and functional — Setup's core loop (read codebase, present findings, write enrichments) works on Codex. Note it as CC-recommended in any user-facing docs.

2. **`ana.json` `platforms` auto-detection on re-init.** Confirmed: first-init only. But what if a user deletes their `ana.json` and re-inits? The `ana.json` is gone, so it looks like a first init — auto-detection runs. If they'd previously had `platforms: ["claude"]` and now have Codex in PATH, they get both. Acceptable? Lean: yes — deleting `ana.json` is an explicit reset.

## Exploration Findings

### Patterns Discovered
- `createClaudeConfiguration()` (assets.ts:169-265): 100 lines, handles fresh + re-init with merge-not-overwrite for agents, delegates skills to `scaffoldAndSeedSkills()`, creates CLAUDE.md and AGENTS.md. `createCodexConfiguration()` mirrors this shape.
- `scaffoldAndSeedSkills()` (skills.ts:118-232): reads from `templates/.claude/skills/`, writes to the `skillsPath` parameter. Changing the parameter from `.claude/skills` to `.ana/skills` is the primary change. Frontmatter injection happens at content generation time.
- `preserveUserState()` (state.ts:720-731): `...parsed.data` spread preserves all schema-defined fields. New fields survive re-init automatically.
- `AGENT_MAP` in `run.ts:30-37`: maps user-facing suffixes to internal agent names. Codex dispatch extends this with platform-specific spawn logic.
- Init orchestration (index.ts:131): `createClaudeConfiguration()` called unconditionally. Needs platform-conditional gating.
- `generateAgentsMd()` and `generatePrimaryPackageAgentsMd()` (assets.ts:341,596): called inside `createClaudeConfiguration()` at lines 209-210 and 261-262. Already exported. Need to be called from init orchestrator directly for Codex-only support.

### Constraints Discovered
- [TYPE-VERIFIED] `getSkillsDir()` returns `.claude/skills` (platform.ts:30) — hardcoded, not platform-aware. 12 consumer sites across 4 files.
- [TYPE-VERIFIED] `getAgentsDir()` returns `.claude/agents` (platform.ts:20) — hardcoded, not platform-aware. 4 consumer sites across 2 files.
- [OBSERVED] CC agent templates have YAML frontmatter (lines 1-6) with `model:`, `skills:`, `memory:`, `initialPrompt:` — must be stripped from Codex templates. Body `---` horizontal rules (dividers) must NOT be confused with frontmatter delimiters.
- [OBSERVED] 3 "Claude Code's Write tool" references need semantic rewrite: ana-build.md:279, ana-verify.md:292 (worktree path guidance), ana-setup.md:655 (tool name reference).
- [OBSERVED] Learn agent has 5 `skills:` frontmatter reasoning references (lines 123, 140, 147, 360, 371) deeply embedded in its diagnostic model. Cannot be mechanically replaced.
- [OBSERVED] `check.ts:1411` hardcodes "No skills found in .claude/skills/" — residual from Scope 1 (proof finding platform-aware-cli-C4).
- [OBSERVED] `state.ts:964` displays `getSkillsDirRel()` — will automatically update when the helper changes.
- [OBSERVED] `init-flow.test.ts:157` asserts old `claude --agent ana` pattern in CLAUDE.md content — residual from Scope 1.
- [OBSERVED] `proof.ts` stores `promoted_to` paths using `getSkillsDirRel()`. No string equality comparison on these paths — switching from `.claude/skills/` to `.ana/skills/` creates no conflict with existing entries. Both resolve through symlinks.
- [VERIFIED] Git stores symlinks as mode 120000 blobs. `git add .claude/` stages symlink pointer, `git add .ana/` stages content. No circular reference or double-staging. Validation spike confirmed.
- [VERIFIED] Codex `$(cat)` shell expansion: 538-line prompt loaded without TOML errors, zero truncation, sentinel reproduced verbatim. Validation spike 12/12 PASS.

### Test Infrastructure
- 14 test files updated in Scope 1 for display string migration. Scope 2 adds: Codex dispatch tests, symlink skill tests, migration tests, Codex-only project tests.
- `init-flow.test.ts:157` needs updating for CLAUDE.md template content (CC template migration).
- Test count must not decrease (CI: 3 OS × 2 Node).

## For AnaPlan

### Structural Analog
`createClaudeConfiguration()` in `assets.ts` — the exact shape that `createCodexConfiguration()` mirrors. Same parameters (cwd, engineResult, initState), same merge-not-overwrite pattern, same skill delegation. Also: `copyAgentFiles()` in `assets.ts` — the pattern for copying agent templates with merge-not-overwrite.

### Relevant Code Paths
- `packages/cli/src/commands/platform.ts` — `getAgentsDir()`, `getSkillsDir()`, `getSkillsDirRel()`, `getPlatformFlags()`. All four need changes: skills → `.ana/skills` canonical, agents → platform-aware, flags → accept platform parameter.
- `packages/cli/src/commands/run.ts` — `executeRun()`, `AGENT_MAP`, `isExecutableInPath()`. Platform resolution chain added. Codex dispatch logic added alongside CC passthrough.
- `packages/cli/src/commands/init/assets.ts` — `createClaudeConfiguration()` (structural analog), `generateAgentsMd()` and `generatePrimaryPackageAgentsMd()` (extract from `createClaudeConfiguration()`). New: `createCodexConfiguration()`.
- `packages/cli/src/commands/init/skills.ts` — `scaffoldAndSeedSkills()`. Target path parameter changes from `.claude/skills` to `.ana/skills`. Frontmatter gains `name:` field.
- `packages/cli/src/commands/init/index.ts:94-189` — init orchestration. Platform-conditional gating. `--platforms` flag. `generateAgentsMd()` extraction.
- `packages/cli/src/commands/init/state.ts:720-731` — `preserveUserState()`. Skill migration detection and content move.
- `packages/cli/src/commands/init/state.ts:964` — display string, auto-updates via `getSkillsDirRel()`.
- `packages/cli/src/commands/check.ts:797,1301,1411` — `discoverSkills()`, skill path resolution, display string fix.
- `packages/cli/src/commands/proof.ts:1159,1228-1229,1555,1623-1624` — skill glob patterns and `promoted_to` paths. Auto-update via helpers.
- `packages/cli/src/commands/agents.ts:89-90,323-324` — agent and skill directory resolution.
- `packages/cli/src/commands/init/commit.ts:47-63` — `KNOWN_ROOTS` already includes `.codex/` and `.agents/` from Scope 1. No changes needed.
- `packages/cli/templates/.claude/agents/*.md` — 6 CC agent templates. 34 `claude --agent` → `ana run` replacements.
- `packages/cli/templates/CLAUDE.md` — 3 `claude --agent` → `ana run` replacements.
- `packages/cli/templates/.codex/agents/` — 5 new `.md` files (no Learn), 5 new `.agent.toml` files.

### Patterns to Follow
- `createClaudeConfiguration()` — parallel function pattern for `createCodexConfiguration()`
- `copyAgentFiles()` — merge-not-overwrite pattern for Codex agent files
- `AGENT_MAP` in `run.ts` — suffix mapping pattern for platform dispatch
- `AnaJsonSchema` `.optional().default().catch()` — for any new schema fields
- `KNOWN_ROOTS` static array in `commit.ts` — already has `.codex/` and `.agents/` from Scope 1
- `findRunProjectRoot()` in `run.ts` — project root resolution pattern for platform detection

### Known Gotchas
- CC template body `---` horizontal rules must not be confused with YAML frontmatter delimiters when stripping frontmatter from Codex templates. Frontmatter is always the first `---` to second `---` block. Everything after is body.
- `scaffoldAndSeedSkills()` source path (`templatesDir`) reads from `templates/.claude/skills/` — this path is a template source, not a runtime skill path. It stays as-is. Only the `skillsPath` destination parameter changes.
- `generateAgentsMd()` is called in TWO places inside `createClaudeConfiguration()`: fresh path (line 209) and re-init path (line 261). Both need to be extracted to the orchestrator.
- Symlink creation must use relative paths (`../.ana/skills`) not absolute paths. Absolute paths break when the repo is cloned to a different location.
- `migrateSkillsToCanonical()` must handle the case where both `.claude/skills/{name}/` and `.ana/skills/{name}/` have content — use more recent mtime, don't silently overwrite.
- `getPlatformFlags()` line 79 hardcodes `parsed.platforms?.[0] ?? 'claude'` for active platform. Must accept a platform parameter in Spec 2 so `ana run build --platform codex` reads `platformFlags.codex`.
- The init orchestrator must gate CLAUDE.md generation on `platforms.includes('claude')`. Codex-only projects get AGENTS.md but not CLAUDE.md.
- Learn is NOT generated for Codex — no `.md`, no `.agent.toml`. `ana run learn` on Codex detects missing prompt file, shows helpful error.
- `check.ts:1411` display string is a Scope 1 residual (proof finding platform-aware-cli-C4). Fix with `getSkillsDirRel()`.
- `init-flow.test.ts:157` asserts old `claude --agent ana` pattern — breaks when CC templates migrate to `ana run` syntax. Update atomically with template changes.

### Things to Investigate
- **Codex `apply_patch` path resolution in worktrees.** The CC Write tool resolves paths against the launch directory, not the worktree CWD. The Codex templates use platform-neutral absolute-path guidance ("use absolute paths to ensure changes land in the worktree"). Verify during build that this guidance is sufficient for Codex's file-writing mechanics. If `apply_patch` already resolves against CWD, the guidance is conservative but harmless.
- **Setup agent on Codex.** Generated template with line 655 rewrite ("Write each enriched skill file" — drops CC tool reference). But Setup is interactive and benefits from CC conversation features. Investigate whether Codex interactive mode supports the same back-and-forth enrichment flow. If not, Setup should be marked as CC-recommended.
- **TOML manifest field naming — RESOLVED.** The manifest stores `sandbox_mode` (Codex config key). `ana run` reads `manifest.sandbox_mode` and passes it as the `--sandbox` CLI flag value: `--sandbox ${manifest.sandbox_mode}`. The config key (`sandbox_mode`) and the CLI flag (`--sandbox`) are different — Plan should make the mapping explicit in the spec.
