You are working on the Omni Suite — a 3-stage document localization pipeline: OPP (extract) → OL (translate) → ORF (backfill).

## Project Structure

```
Omni_Suite/
├── Omni_Pre_Processor/      # OPP — Document extraction (MD + XLIFF + skeleton)
│   └── src/opp/             # Source: detectors, extractors, pipeline
├── Omni_Localizer/          # OL — Translation (MD / XLIFF)
│   └── src/ol/              # Source: MD bus, XLIFF bus, repair layers
├── Omni_Re_Formatter/       # ORF — Backfill to target format
│   └── src/orf/             # Source: channels, converters, agents
├── tests/                   # Suite-level E2E tests (30+ files)
├── scripts/                 # setup_dev.sh, sync_shallow.sh, install_md2pptx.sh
├── .venv_ol/                # Unified venv (Python 3.13)
├── pyproject.toml           # Root workspace
├── .pre-commit-config.yaml  # Pre-commit hooks
├── Makefile                 # Dev targets (test, lint, clean, setup)
├── AGENTS.md                # Agent guidance (this file)
├── CLAUDE.md                # Claude Code architecture context
└── .cursorrules             # Cursor agent context (this file)
```

## Module Details

| Module | Path | CLI | MCP Server | Test Dir |
|--------|------|-----|------------|----------|
| OPP | `Omni_Pre_Processor/` | `opp` | `opp-mcp-server` | `Omni_Pre_Processor/tests/` |
| OL | `Omni_Localizer/` | `ol` | `ol-mcp` | `Omni_Localizer/tests/` |
| ORF | `Omni_Re_Formatter/` | `orf` | `orf-mcp-server` | `Omni_Re_Formatter/tests/` |

## MCP Server Names

Configure in your tool's MCP settings. **Note:** naming is not uniform —
`opp-mcp-server` and `orf-mcp-server` use the `-server` suffix, but `ol-mcp`
does NOT.

- `opp-mcp-server` — **7 tools**: `extract_document`, `batch_extract`, `detect_format_tool`, `generate_markdown`, `generate_xliff`, `save_skeleton`, `ping`
- `ol-mcp` (no `-server` suffix) — **8 tools**: `translate_md_text`, `translate_xliff`, `judge_text`, `load_glossary`, `get_relevant_terms`, `search_tm`, `batch_translate_texts`, `ping`
- `orf-mcp-server` — **6 tools** (not 7): `apply_md`, `apply_xliff`, `batch_convert`, `detect_format`, `info`, `ping`

## MCP Path Allowlists

**Preferred**: Set `MCP_ALLOWED_DIRECTORIES` once — it is read by all 3 MCP servers (OPP, OL, ORF).

Per-module overrides (checked first, fall back to `MCP_ALLOWED_DIRECTORIES`):
- OPP: `OPP_MCP_ALLOWED_DIRS` (e.g. `/docs:/tmp/out`). **NOT** `OPP_ALLOWED_DIRECTORIES` (CLI-only).
- OL: `OL_ALLOWED_DIRECTORIES` (or configured in `ol_mcp/config.py`).
- ORF: `ORF_MCP_ALLOWED_DIRS` (defaults to `[Path.cwd()]` if unset — **production should always set it explicitly**).

OPP is fail-closed (`ValueError: allowed_directories cannot be empty` if unset).
ORF is fail-open (defaults to CWD). OL configures via file, not env.

## MCP stdio workaround (FastMCP 3.4.2 bug)

If MCP servers fail to respond to stdio JSON-RPC, use `scripts/mcp_bridge.py`
(raw JSON-RPC over stdin/stdout, no `mcp` library). See `../ACCEPTED_GAPS.md` line 18.

## CLI frameworks differ

- OPP: argparse (`opp`)
- OL: typer (`ol`)
- ORF: click (`orf`)

Flag names are uniform (`--kebab-case`). Help-text styles differ. See `../docs/DECISIONS.md` ADR 0001.

## Testing Conventions

- Always set `OMNI_TEST_FAKE_LLM=1` and `OMNI_TEST_FAKE_PANDOC=1` for offline test runs
- Python 3.13 via `.venv_ol/bin/python`
- Test markers: `nightly` (real LLM, skipped by default), `slow`, `e2e`, `requires_opp`, `requires_ol`, `requires_orf`
- Run `pytest tests/ -m "not nightly"` for quick suite checks
- Pre-commit hooks: `gitleaks`, `check-added-large-files`, `check-merge-conflict`, `detect-private-key`, `end-of-file-fixer`, `trailing-whitespace`, `omni-contract-smoke`

## Key Commands

```bash
source .venv_ol/bin/activate
make test-quick      # Quick suite check
make test            # Full CI-mode test run
make smoke           # Contract smoke test
make lint            # Pre-commit on all files
make clean           # Clean artifacts
```
