{"owner":"lidge-jun","repo":"opencodex","hasSkills":true,"totalSkillsCount":4,"totalTokensCount":7134,"categories":["root-instruction","mcp-config","marketplace","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"opencodex":{"command":"npx","args":["-y","@modelcontextprotocol/server-opencodex"]}}},"found":["AGENTS.md",".cursor/mcp.json",".claude-plugin/marketplace.json",".claude-plugin/plugin.json"],"skills":{"AGENTS.md":"# AGENTS.md\n\nGuidance for AI agents (and humans) working on or reviewing this repository.\n\n## What this project is\n\nopencodex (`ocx`) is a universal provider proxy for OpenAI Codex and Claude Code:\none local proxy that lets Codex CLI/App/SDK and Claude Code use many LLM\nproviders (Claude, Gemini, Grok, DeepSeek, Ollama, and more). The runtime is\nBun-native TypeScript with no separate server compile step.\n\n## Repository layout\n\n- `src/` — proxy runtime: routing, provider adapters, config, management API.\n- `tests/` — Bun tests in domain directories that mirror `src/`\n  (`tests/<domain>/*.test.ts`; `providers/` and `adapters/` have one more\n  level for the larger vendors). The explicit map is\n  `scripts/test-layout/layout.json` plus its `explicitShards` sibling maps, with regex seeds and migration state in\n  `scripts/test-layout/seeds.json`; `tests/test-layout.test.ts` enforces that every file resolves to a\n  domain and sits in it, and only the two layout guards live at the root.\n  Shared helpers in `tests/helpers/`, fixtures in `tests/fixtures/`, broader\n  scenarios in `tests/e2e-style/`. Source-oracle tests resolve the repository\n  through `tests/helpers/repo-root.ts` (`repoRoot()`, `repoPath()`,\n  `helperPath()`, `fixturePath()`), never `import.meta.dir + \"/..\"`. A new\n  test file lands in its domain directory and needs an entry in both\n  `layout.json` `explicit` (or its domain shard) and a `tests/fixtures/test-layout-expected*.json` shard\n  (`tests/test-layout-tooling.test.ts` names the missing one); the regex\n  seeds in `seeds.json` place a conventionally named file until then.\n  History: `devlog/_fin/260905_test_modularization_and_windows/`.\n- `gui/` — React + Vite dashboard; packaged output is served from `gui/dist`.\n- `app/` — native macOS WidgetKit extension bundled into the Tauri desktop app;\n  `MenuBarCore` is its snapshot model/formatting layer. Its tests are\n  executables, not XCTest bundles — Command Line Tools ships neither a usable\n  XCTest module nor the swift-testing runtime.\n- `docs-site/` — public docs (Astro + Starlight), deployed to GitHub Pages.\n- `go/` — retired Go native-runtime experiment; kept only where the TypeScript\n  runtime still references it. New work does not go here.\n- `structure/` — maintainer invariants and architecture notes; read before\n  changing shared subsystems. [`structure/INDEX.md`](./structure/INDEX.md) is the\n  reading order and the source-ownership table, and\n  [`structure/AGENTS.md`](./structure/AGENTS.md) holds the rules for changing\n  anything in there. Ownership is not advisory: changing an owned source area\n  obliges the same change to update its doc, and `bun run structure:check`\n  (wired into the suite by `tests/ci-workflows/structure-ssot.test.ts`) fails on a\n  doc that names a path this tree no longer has, on an invariant whose test is\n  gone, and on a new `src/` area nobody claimed.\n- `scripts/` — release and maintenance tooling; `scripts/release.ts` is the\n  release authority.\n- `desktop/` — Tauri v2 desktop shell, bootstrap UI, and compiled proxy sidecar preparation.\n- `devlog/` — planning and investigation notes, tracked in this repository. See\n  \"The `devlog` directory\" below for what may and may not go there.\n\nRead the nearest nested `AGENTS.md` before changing files in a scoped\ndirectory (`src/`, `gui/`, `docs-site/`, `scripts/`, `.github/`).\n\n## Optional subsystems stay off the core path\n\n`src/lab/` (Compatibility Lab) is opt-in. A user who configures one provider and\none model — no routing profile, no Lab — must execute no Lab code and start no\nLab timer.\n\nThree files carry every such user's request path and must not reach `src/lab/`,\ndirectly or transitively:\n\n- `src/router.ts`\n- `src/server/lifecycle.ts`\n- `src/server/responses/core.ts`\n\n`tests/lab/core-lab-boundary.test.ts` enforces this by walking the runtime import\ngraph and printing the offending chain on failure. It is not a style rule: the\noriginal violation hid in a six-hop chain\n(`assemble → quota → auth-api → native-main-admission → lifecycle → lab`) where\nno single file looked wrong, and it pulled ~69 Lab modules into every install.\n\nAn optional subsystem registers into a core-owned slot at activation instead of\nbeing imported. The existing seams are `src/server/passive-route-linker.ts`,\n`src/routing/compatibility/provider-slot.ts`, and\n`src/lib/optional-shutdown-hooks.ts`.\n\n`src/server/index.ts` is deliberately exempt: a composition root is supposed to\nknow which optional subsystems exist. Its obligation is the gate, not the import\n— activation must stay behind `labActivationRequired`, and it must stay\nsynchronous. Everything between `Bun.serve` and the return of `startServer` runs\nin one synchronous turn, which is what guarantees a policy route can never be\nevaluated before its evidence provider is registered. The synchronous\nsubagent-fallback chain has nowhere to await, so an `await` added before the\nactivation block would silently reroute subagents to a different model than the\noperator configured.\n\nThat one is enforced too, in the same file: a scan reads the window between the\n`Bun.serve` call and the `labActivationRequired` check and fails on any `await`\nthat would suspend `startServer` itself, plus on `startServer` being declared\n`async`. It has to ignore comments, string bodies, and nested functions to be\nusable, because the window legitimately contains three awaits inside the\n`server.stop` closure and two comments that mention the word. Until it existed,\nthis paragraph was the only thing holding the guarantee.\n\nDesign and audit history: `devlog/_fin/260814_lab_core_decoupling/`.\n\n## The `devlog` directory\n\nPlanning notes, triage matrices, and investigation artifacts live in `devlog/`,\ntracked like any other documentation. There is no submodule and no private\nmirror. It was a private submodule until the pointer churn outgrew its value:\n1723 commits touched the gitlink, and `dev`, `preview`, and `main` each carried a\ndifferent pointer, so every branch move and promotion dragged a diff.\n\n- `devlog/_plan/` — units still open, one directory per unit, decade-numbered\n  docs.\n- `devlog/_fin/` — closed units, moved here once a terminal outcome is recorded.\n  A `_fin` unit is a record of work already visible in public git history.\n- `devlog/_chase/` — external reference material for parity comparisons.\n  Reference *clones* are gitignored: they are third-party source carrying their\n  own licenses and have no business in this repository's history.\n\nNothing in the build, typecheck, or test path reads from `devlog/`, so a\ncontributor who ignores it entirely still passes every gate. `privacy:scan` does\nread it — that is deliberate, and it is what makes a public devlog safe rather\nthan merely visible.\n\nTwo mechanical guards in `tests/ci-workflows/repo-hygiene.test.ts` back this up: no `160000`\ngitlink may be tracked anywhere, and neither the vendored reference clones nor\nthe security triage excised before publication may reappear in the index. Both\nwere driven red once to prove they are not vacuous. The gitlink assertion exists\nbecause a gitlink in a tree CI does not initialize breaks `actions/checkout` for\nevery contributor, which happened twice.\n\n## Security working notes\n\n**Security work is done in scratch space, never in a tracked directory.** That\nincludes unreleased findings, severity assessments, draft advisories, exploit\nor bypass reasoning, reproduction steps for an unfixed defect, and\npre-disclosure patch plans.\n\nUse `.tmp/` in the working tree (already gitignored) or a `mktemp -d` path.\n`devlog/` is **not** an acceptable location — it is a public directory in a\npublic repository, so anything committed there is disclosed the moment it is\npushed, and the history is not practical to purge afterwards. A private\nrepository is not acceptable either: it gets cloned across machines and CI and\noutlives the embargo.\n\n**This binds maintainers exactly as it binds contributors and agents.** The rule\nhas been violated by maintainer-authored triage before: two units of open\nsecurity review accumulated under `devlog/_plan/` and had to be excised before\nthis directory could be published. Seniority is not an exemption, and \"it is\nonly in the private half\" is no longer a thing that exists.\n\nThe test to apply before writing a security note into `devlog/`: **is there\nalready a public diff that reveals this weakness?** If the fix has shipped, the\nwriteup discloses nothing new and belongs in `_fin/`. If it has not, the note is\npre-disclosure material and goes to scratch. That distinction is why closed\nhardening records stay in the tree while open triage does not.\n\nOnly the published outcome reaches a repository — the fix itself, its\nregression test, the release note, the advisory once it is public. Draft the\nadvisory in scratch space and delete the scratch directory once the advisory is\nlive.\n\nThis applies to `AGENTS.md`-following agents as much as to humans. If a task\nasks you to write up a security finding, put the write-up in scratch space and\nsay where it is; do not add it to `devlog/`, `structure/`, or `docs-site/`.\n\n## User-consent actions\n\nSome actions write to the **user's own accounts and identity** rather than to\nthis repository, and an agent must never perform or auto-answer them. One example\nis starring the repository on GitHub, which only comes up when\nan agent is *running* opencodex — not when it is working on this codebase.\n\nThe rule lives in [`AGENTS_INSTALL.md`](./AGENTS_INSTALL.md), which is the file\nan installing or operating agent reads. It was moved out of here because a\ndevelopment-facing file is the wrong place to trigger on it: this file is loaded\nfor every code change, and the consent boundary applies to none of them.\n\nWhat matters for development work: the enforcement is code, not prose —\n[`src/cli/agent-driven.ts`](./src/cli/agent-driven.ts),\n[`src/cli/star-prompt.ts`](./src/cli/star-prompt.ts), and\n[`src/server/management/sidebar-routes.ts`](./src/server/management/sidebar-routes.ts),\ncovered by `tests/server/startup-prompt.test.ts`, `tests/cli/agent-driven.test.ts`, and\n`tests/server/sidebar-routes.test.ts`. If you add another action that spends the user's\nidentity, credits, or reputation, gate it the same way rather than relying on a\nprompt an agent can answer, and document it in `AGENTS_INSTALL.md`.\n\n**Be clear about what that enforcement is and is not.** The management endpoint\nrequires a dashboard session, which stops the casual path — an agent that would\nhave POSTed there because the endpoint existed, and one holding only the admin\ntoken. It is not a technical barrier against a determined local agent: a process\nrunning as the user can mint its own session from the loopback dashboard\nbootstrap, and can skip the proxy entirely by running `gh` itself. Every local\ncredential is equally reachable by both the browser and the agent, so no check\ninside this process can tell them apart. The real boundary is the rule above, and\nit binds you regardless of which mechanism is within reach.\n\n## Commands\n\n```bash\nbun install\nbun run typecheck      # bun x tsc --noEmit (strict)\nbun run test:changed   # import-graph tests against the resolved `dev` merge base\nbun run test           # full tests/ suite (default before review)\nbun run lint:gui       # GUI eslint\nbun run privacy:scan   # credential/privacy scan used by CI\nbun run structure:check # structure/ doc-map, ownership, and invariant-binding gate\nbun run structure:index # regenerate structure/INDEX.md from structure/manifest.json\nbun run build:gui      # Vite GUI build\n```\n\n`skills/ocx/` is the operating reference for the CLI — what an agent reads to *drive* a running\nproxy, as opposed to [`AGENTS_INSTALL.md`](./AGENTS_INSTALL.md) (installing and operating consent)\nor this file (changing the codebase). Its surface map is generated:\n\n```bash\nbun run skill:surface        # regenerate after adding a capability\nbun run skill:surface:check  # what CI asserts\n```\n\n`tests/ci-workflows/skill-ocx.test.ts` fails if the committed map drifts from `src/cli/capabilities.ts`, and\nalso if the hand-written pages name a command the registry does not have. That second check is not\nhypothetical: it caught a documented `ocx request-history` that never existed.\n\nRun the test suite for a change; `bun run test` is the default before a\nnon-trivial PR is marked review-ready or approved. During implementation, use\nfocused files or `bun run test:changed` for faster feedback.\n\nIf a full local run is disproportionately expensive for the task or available\nresources, including contention across concurrent worktrees, run at least the\nfocused regression tests that exercise the changed behavior. This is a scope\nexception, not permission to skip testing or ignore a failing test. Record why\nthe full run was impractical, the exact commands and results, and the coverage\nleft to CI in the PR's Verification section. Never describe an unrun suite as\npassing. Run `bun run typecheck` before review readiness as well.\n\n`bun run test:changed` follows Bun's parsed module graph, so it cannot discover\ndependencies expressed through subprocesses, source files read as data, or\ngolden/derived files. Run those relevant regression files explicitly. If a\nfocused set cannot reliably cover the change, keep the PR in draft until the\nbroader validation is available.\n\nAfter pushing, inspect the required CI for the current PR head. Missing,\nawaiting-approval, skipped, cancelled, or older-head results are not passing\nevidence. Required checks must actually complete successfully before merge.\nThe repository does not install an automatic pre-push validation hook;\n`bun run prepush` remains available as an explicit comprehensive check.\n\nDo not rerun passing checks on unchanged code merely for additional confidence.\n\n## What a green pull request does not tell you\n\nExact-head CI cannot see a defect that exists only in the union of two changes. Each\nbranch is correct at its own head, the merge is not, and the failure lands on whoever\npushes next. One round produced ten of these, including an hour of red `dev`, so the\nclasses below are worth checking before you push rather than after.\n\n### The file-size ratchet has almost no headroom\n\n`tests/fixtures/file-size-baseline.json` records a line cap per file and\n`updateBaseline` uses `Math.min`, so a cap only ever moves **downward**. Raising one is\nnot possible by design, and a cap is not a suggestion you can negotiate with.\n\nAt the time of writing, 39 of the 51 tracked files sit at **exactly** their cap and three\nmore are within five lines. Among them are `src/server/index.ts`, `src/config.ts`,\n`src/server/responses/core.ts`, `gui/src/pages/Models.tsx`, and the large test files\n`tests/codex-integration/codex-catalog.test.ts` and\n`tests/responses/openai-responses-passthrough.test.ts`. Adding one line to any of them\nfails `file-size ratchet: repository` for your branch and for every branch cut from\n`dev` afterwards.\n\nTwo branches can each stay under a cap alone and sum over it together; that is what\nhappened in #4908, #5011 and #5018. The remedy is always a move, never a number: put the\nnew case in a sibling file, byte for byte, and register it in **both**\n`scripts/test-layout/layout.json` (or its domain shard) and a `tests/fixtures/test-layout-expected*.json` shard.\n`d3ca5522db` is the original precedent.\n\nA moved test is not automatically the same test. One case moved out of\n`codex-v2-gate.test.ts` failed in isolation and then failed again in place once unrelated\nblocks moved around it, because its final assertion was reading catalog state earlier\ncases had warmed rather than the contract. If a moved case changes colour, suspect the\ncase before the move.\n\n### Anything exhaustive over a union\n\nA locale catalog, a `satisfies Record<Union, ...>`, a hand-maintained roster, a count in\ngenerated documentation. Adding a member to the union in one branch while another branch\nadds a consumer keyed by it produces a merge that typechecks in neither direction.\n\n`typecheck` precedes every job, so one missing member is not one red suite. It took down\nfourteen checks on `dev` — all four test shards, `docker smoke`, `storage policy`,\n`api usage` and all three `npm-global` smokes — when a closed translation namespace still\nlisted nine locales after a tenth had landed.\n\nCounts drift the same way and more quietly, because both sides write a plausible number.\nTwo branches each added one CLI capability and each wrote `47`; the merged truth was 48.\nTwo each added one provider preset and each wrote `94`; the registry had 95. Prefer\nderiving a count or a member list from the thing it describes over restating it.\n\n## Minimal containers and agent sandboxes\n\nFresh dev containers and agent sandboxes (Cursor Cloud, devcontainers, CI\nimages) often ship Node but not Bun. Install it first:\n\n```bash\ncurl -fsSL https://bun.sh/install | bash   # installs ~/.bun/bin/bun\nexport PATH=\"$HOME/.bun/bin:$PATH\"\nbun install && (cd gui && bun install)\n```\n\nRun the proxy with `bun run src/cli/index.ts start --port <port>`. `/healthz`\nreports status, `/` serves the dashboard, and the management API requires the\nadmin token the server writes to `$OPENCODEX_HOME/admin-api-token` at startup.\n\n`bun run test` has five known environment-only failures in such containers.\nThey are not regressions; do not re-investigate them:\n\n- `service diagnostics > status summary exposes the service log path`,\n  `CLI subcommand help > status prints diagnostics without starting the proxy`,\n  and `CLI subcommand help > invalid service and codex-shim usage include\n  remove alias` require a running systemd init; in a container PID 1 is\n  typically `tini` or another minimal init, so service commands report\n  \"systemd not found\".\n- `package tree integrity > an in-place rewrite of the same byte length is\n  still a replacement` and `Codex Log Guard inspection > repeat inspection is\n  memoized and invalidated by a write` rely on filesystem mtime granularity\n  that some container filesystems do not provide.\n\nEverything else passes (15480 pass / 16 skip / 5 fail as of 2.35.0).\n\n## Issues and pull requests (agents)\n\nAgent-created issues and PRs must use the repository templates. The gates\nbelow enforce them, so a freeform or mismatched submission is rejected rather\nthan nudged.\n\n- **Creating an issue:** open it through the template chooser and use the\n  matching form in `.github/ISSUE_TEMPLATE/` — `bug_report.yml` (Bug report),\n  `feature_request.yml` (Feature proposal), `documentation.yml`\n  (Documentation), or `provider_compatibility.yml` (Provider or API\n  compatibility). Keep the form's section headings exactly as generated;\n  `enforce-issue-quality` validates the headings and closes untemplated or\n  mislabeled issues (`.github/ISSUE_TEMPLATE/config.yml` disables blank\n  issues, so there is no freeform fallback).\n- **Opening a pull request:** fill every section of\n  `.github/PULL_REQUEST_TEMPLATE.md` (Summary, Verification, Checklist).\n  `enforce-target` rejects empty, thin, or malformed descriptions. If the PR\n  changes files under `gui/`, include a screenshot of the UI change in the\n  description; the check re-runs on description edits until the screenshot is\n  present. Drag the image into the description editor rather than committing it:\n  an image on your branch rides the squash merge into `dev`. Maintainers\n  uploading from the command line use the `pr-assets` branch and link by commit\n  SHA. Never commit screenshot evidence to the PR branch — the squash merge carries\n  it into `dev`, which is how `docs/pr-assets/` and its siblings grew until\n  they were deleted; `tests/ci-workflows/repo-hygiene.test.ts` now rejects\n  those folders. When the PR resolves an issue, add\n  `Closes #<number>` to link it. GitHub auto-closes the linked issue only\n  when the PR merges into the default branch (`main`); PRs here target\n  `dev`, so close the issue manually once the change is on `dev`.\n- **Landing another author's work:** reimplementing, superseding, carrying, or\n  rebasing someone else's pull request requires a `Co-authored-by` trailer\n  naming that author, in the description or in a branch commit so it survives\n  the squash. Saying it in prose is not equivalent — the trailer is what GitHub\n  reads for the contributor graph, and a sentence in a commit body is read by\n  nothing. This repository did it both ways for months: `53c09a247` says \"Clean\n  reimplementation of #3193\" and names the author in a trailer, `5734a1caf` says\n  \"Reimplements #2797 by @rrmlima\" and names nobody, so that contribution is\n  invisible on its author's profile. The 27 landings already in that state are\n  recorded in [`CREDITS.md`](./CREDITS.md); `missing_coauthor_credit` in\n  `.github/scripts/pr-carry-attribution.cjs` is why the list should not grow.\n\n## CI policy (maintainer decision — do not change)\n\n- Pull requests run **only the fast Ubuntu subset**.\n- Full cross-platform (macOS/Windows) CI runs **only** via manual\n  `workflow_dispatch` and on push to `main`/`preview` (release lines).\n- **Never** add a `dev` push trigger, or `pull_request` runs for macOS/Windows\n  jobs, to any workflow. PRs that change this policy will be closed.\n\n## Branch policy\n\n- `dev` — the single integration branch and the target for every pull request.\n- `main` — release branch. It only moves by maintainer-controlled promotion\n  from `dev` (releases, docs deploys). Do not open feature PRs against `main`.\n- `preview` — prerelease train (`x.y.z-preview.*` versions).\n\nBun-native TypeScript on `dev` is the only runtime line. If native code\nreturns, the expectation is an incremental module (for example Rust via N-API)\nlanding on `dev`, not a second full-runtime branch.\n\nStacked child pull requests that target another **open** PR's head branch are\nan intentional review workflow, not an alternate integration line. The\n**`enforce-target`** check skips the wrong-base gate for those children; after\nthe parent lands or closes, retarget the child to `dev`.\n\nRebase pull requests are welcome. Bringing a stale branch onto the current head\nis ordinary maintenance — open it as a normal pull request and name the source\ncommits in the description.\n\nThe **`enforce-target`** CI check rejects pull requests whose head\nancestry sits on the **`main`** tip while far behind **`dev`**, and rejects\nempty, thin, or malformed descriptions. If changed paths include files under\n`gui/`, include a screenshot of the UI change in the description; the check\nre-runs on description edits until the screenshot is present. Drag the image\ninto the description editor rather than committing it, or, when uploading from\nthe command line, use the `pr-assets` branch and link by commit SHA.\nContributor PRs (authors without repository push permission) open in draft and\nstay there until a four-box review-readiness checklist in the description is\ncomplete: required local validation passed with its scope documented, branch\non the latest `dev` commit, all correct Codex and CodeRabbit findings fixed,\nand the ready-for-review confirmation. When all\nfour boxes are ticked the gate marks the PR ready and notifies the maintainers\nlisted in `MAINTAINERS.md` (excluding the author). Completion is bound to the\nexact commit the PR head pointed at: if new commits are pushed afterwards, the\ngate moves the PR back to draft, resets the checklist and the notification,\nand asks the author to test and tick the boxes again against the latest code.\nBefore a completion is accepted, the gate verifies the checklist claims it\ncan check itself: the branch must be on the latest `dev` commit or at most\n10 commits behind it, and Codex/CodeRabbit findings must be resolved. The\nlocal-validation box is an author attestation only — fork contributors cannot start\nrepository CI; a maintainer has to — so the gate never disproves it; a new\npush still resets every box. A disproved claim unticks the matching box and\nkeeps the PR a draft.\nAuthors with repository push permission skip the ancestry heuristic only. As with approval requirements in\n[`MAINTAINERS.md`](./MAINTAINERS.md), the ancestry heuristic is a CI check\nrather than a branch rule. The branches themselves are protected: `dev`,\n`main`, and `preview` each require a pull request and block force-pushes and deletion.\nFor `dev` only, a current maintainer with GitHub `maintain` or `admin` access may\nexplicitly integrate through a PR without another maintainer approval, including\ntheir own PR, under the policy in `MAINTAINERS.md`. Record the decision and exact-head\nCI evidence; keep outstanding maintainer objections and security review separate.\nThe bypass is PR-only, so a direct push to `dev` remains rejected regardless of\nlocal hook settings. Contributor review and `main`/`preview` rules remain unchanged.\n\n[`MAINTAINERS.md`](./MAINTAINERS.md) is authoritative for review and merge\npolicy (approvals, CI requirements, security review, promotion). This file\nsummarizes; it never overrides it.\n\n## Review guidelines\n\nThese rules apply to all code reviews on this repository, including automated\nreviewers (Codex, CodeRabbit).\n\n- **Language:** always review in English, regardless of the PR or issue\n  language. Be detailed and specific: name the file and line, describe the\n  concrete failure mode, and suggest a fix. Avoid vague or purely stylistic\n  commentary.\n- **Branch targeting:** flag any pull request that does not target `dev`\n  (releases and maintainer promotions are the only exceptions).\n- **Security boundary (highest priority):** changes touching authentication,\n  credential/token handling, OAuth flows, GitHub Actions workflows, release\n  automation (`scripts/release.ts`, `.github/workflows/release.yml`), or\n  dependency installation require explicit security review per\n  `MAINTAINERS.md`. Treat token logging/serialization, secret exposure,\n  workflow permission escalation, and mutable third-party action refs as\n  release blockers.\n- **Runtime constraints:** the proxy is Bun-native. Flag Node-only APIs,\n  assumptions about a compile step, or code paths that break `bun run\n  typecheck` / `bun run test`.\n- **Tests:** behavior changes in `src/` need a focused regression test near\n  the existing tests for that subsystem. During implementation, run the relevant\n  focused files and use `bun run test:changed` for import-connected coverage as\n  described above. Full-suite validation is the default before review readiness;\n  the documented resource exception still requires focused regression tests.\n- **Docs sync:** user-facing behavior changes should update `docs-site/` (and\n  keep translated locales from contradicting the English source).\n- **Privacy:** `bun run privacy:scan` must stay green; never introduce logging\n  of request bodies, API keys, or account identifiers.\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.claude-plugin/plugin.json)"},"files":{"AGENTS.md":"# AGENTS.md\n\nGuidance for AI agents (and humans) working on or reviewing this repository.\n\n## What this project is\n\nopencodex (`ocx`) is a universal provider proxy for OpenAI Codex and Claude Code:\none local proxy that lets Codex CLI/App/SDK and Claude Code use many LLM\nproviders (Claude, Gemini, Grok, DeepSeek, Ollama, and more). The runtime is\nBun-native TypeScript with no separate server compile step.\n\n## Repository layout\n\n- `src/` — proxy runtime: routing, provider adapters, config, management API.\n- `tests/` — Bun tests in domain directories that mirror `src/`\n  (`tests/<domain>/*.test.ts`; `providers/` and `adapters/` have one more\n  level for the larger vendors). The explicit map is\n  `scripts/test-layout/layout.json` plus its `explicitShards` sibling maps, with regex seeds and migration state in\n  `scripts/test-layout/seeds.json`; `tests/test-layout.test.ts` enforces that every file resolves to a\n  domain and sits in it, and only the two layout guards live at the root.\n  Shared helpers in `tests/helpers/`, fixtures in `tests/fixtures/`, broader\n  scenarios in `tests/e2e-style/`. Source-oracle tests resolve the repository\n  through `tests/helpers/repo-root.ts` (`repoRoot()`, `repoPath()`,\n  `helperPath()`, `fixturePath()`), never `import.meta.dir + \"/..\"`. A new\n  test file lands in its domain directory and needs an entry in both\n  `layout.json` `explicit` (or its domain shard) and a `tests/fixtures/test-layout-expected*.json` shard\n  (`tests/test-layout-tooling.test.ts` names the missing one); the regex\n  seeds in `seeds.json` place a conventionally named file until then.\n  History: `devlog/_fin/260905_test_modularization_and_windows/`.\n- `gui/` — React + Vite dashboard; packaged output is served from `gui/dist`.\n- `app/` — native macOS WidgetKit extension bundled into the Tauri desktop app;\n  `MenuBarCore` is its snapshot model/formatting layer. Its tests are\n  executables, not XCTest bundles — Command Line Tools ships neither a usable\n  XCTest module nor the swift-testing runtime.\n- `docs-site/` — public docs (Astro + Starlight), deployed to GitHub Pages.\n- `go/` — retired Go native-runtime experiment; kept only where the TypeScript\n  runtime still references it. New work does not go here.\n- `structure/` — maintainer invariants and architecture notes; read before\n  changing shared subsystems. [`structure/INDEX.md`](./structure/INDEX.md) is the\n  reading order and the source-ownership table, and\n  [`structure/AGENTS.md`](./structure/AGENTS.md) holds the rules for changing\n  anything in there. Ownership is not advisory: changing an owned source area\n  obliges the same change to update its doc, and `bun run structure:check`\n  (wired into the suite by `tests/ci-workflows/structure-ssot.test.ts`) fails on a\n  doc that names a path this tree no longer has, on an invariant whose test is\n  gone, and on a new `src/` area nobody claimed.\n- `scripts/` — release and maintenance tooling; `scripts/release.ts` is the\n  release authority.\n- `desktop/` — Tauri v2 desktop shell, bootstrap UI, and compiled proxy sidecar preparation.\n- `devlog/` — planning and investigation notes, tracked in this repository. See\n  \"The `devlog` directory\" below for what may and may not go there.\n\nRead the nearest nested `AGENTS.md` before changing files in a scoped\ndirectory (`src/`, `gui/`, `docs-site/`, `scripts/`, `.github/`).\n\n## Optional subsystems stay off the core path\n\n`src/lab/` (Compatibility Lab) is opt-in. A user who configures one provider and\none model — no routing profile, no Lab — must execute no Lab code and start no\nLab timer.\n\nThree files carry every such user's request path and must not reach `src/lab/`,\ndirectly or transitively:\n\n- `src/router.ts`\n- `src/server/lifecycle.ts`\n- `src/server/responses/core.ts`\n\n`tests/lab/core-lab-boundary.test.ts` enforces this by walking the runtime import\ngraph and printing the offending chain on failure. It is not a style rule: the\noriginal violation hid in a six-hop chain\n(`assemble → quota → auth-api → native-main-admission → lifecycle → lab`) where\nno single file looked wrong, and it pulled ~69 Lab modules into every install.\n\nAn optional subsystem registers into a core-owned slot at activation instead of\nbeing imported. The existing seams are `src/server/passive-route-linker.ts`,\n`src/routing/compatibility/provider-slot.ts`, and\n`src/lib/optional-shutdown-hooks.ts`.\n\n`src/server/index.ts` is deliberately exempt: a composition root is supposed to\nknow which optional subsystems exist. Its obligation is the gate, not the import\n— activation must stay behind `labActivationRequired`, and it must stay\nsynchronous. Everything between `Bun.serve` and the return of `startServer` runs\nin one synchronous turn, which is what guarantees a policy route can never be\nevaluated before its evidence provider is registered. The synchronous\nsubagent-fallback chain has nowhere to await, so an `await` added before the\nactivation block would silently reroute subagents to a different model than the\noperator configured.\n\nThat one is enforced too, in the same file: a scan reads the window between the\n`Bun.serve` call and the `labActivationRequired` check and fails on any `await`\nthat would suspend `startServer` itself, plus on `startServer` being declared\n`async`. It has to ignore comments, string bodies, and nested functions to be\nusable, because the window legitimately contains three awaits inside the\n`server.stop` closure and two comments that mention the word. Until it existed,\nthis paragraph was the only thing holding the guarantee.\n\nDesign and audit history: `devlog/_fin/260814_lab_core_decoupling/`.\n\n## The `devlog` directory\n\nPlanning notes, triage matrices, and investigation artifacts live in `devlog/`,\ntracked like any other documentation. There is no submodule and no private\nmirror. It was a private submodule until the pointer churn outgrew its value:\n1723 commits touched the gitlink, and `dev`, `preview`, and `main` each carried a\ndifferent pointer, so every branch move and promotion dragged a diff.\n\n- `devlog/_plan/` — units still open, one directory per unit, decade-numbered\n  docs.\n- `devlog/_fin/` — closed units, moved here once a terminal outcome is recorded.\n  A `_fin` unit is a record of work already visible in public git history.\n- `devlog/_chase/` — external reference material for parity comparisons.\n  Reference *clones* are gitignored: they are third-party source carrying their\n  own licenses and have no business in this repository's history.\n\nNothing in the build, typecheck, or test path reads from `devlog/`, so a\ncontributor who ignores it entirely still passes every gate. `privacy:scan` does\nread it — that is deliberate, and it is what makes a public devlog safe rather\nthan merely visible.\n\nTwo mechanical guards in `tests/ci-workflows/repo-hygiene.test.ts` back this up: no `160000`\ngitlink may be tracked anywhere, and neither the vendored reference clones nor\nthe security triage excised before publication may reappear in the index. Both\nwere driven red once to prove they are not vacuous. The gitlink assertion exists\nbecause a gitlink in a tree CI does not initialize breaks `actions/checkout` for\nevery contributor, which happened twice.\n\n## Security working notes\n\n**Security work is done in scratch space, never in a tracked directory.** That\nincludes unreleased findings, severity assessments, draft advisories, exploit\nor bypass reasoning, reproduction steps for an unfixed defect, and\npre-disclosure patch plans.\n\nUse `.tmp/` in the working tree (already gitignored) or a `mktemp -d` path.\n`devlog/` is **not** an acceptable location — it is a public directory in a\npublic repository, so anything committed there is disclosed the moment it is\npushed, and the history is not practical to purge afterwards. A private\nrepository is not acceptable either: it gets cloned across machines and CI and\noutlives the embargo.\n\n**This binds maintainers exactly as it binds contributors and agents.** The rule\nhas been violated by maintainer-authored triage before: two units of open\nsecurity review accumulated under `devlog/_plan/` and had to be excised before\nthis directory could be published. Seniority is not an exemption, and \"it is\nonly in the private half\" is no longer a thing that exists.\n\nThe test to apply before writing a security note into `devlog/`: **is there\nalready a public diff that reveals this weakness?** If the fix has shipped, the\nwriteup discloses nothing new and belongs in `_fin/`. If it has not, the note is\npre-disclosure material and goes to scratch. That distinction is why closed\nhardening records stay in the tree while open triage does not.\n\nOnly the published outcome reaches a repository — the fix itself, its\nregression test, the release note, the advisory once it is public. Draft the\nadvisory in scratch space and delete the scratch directory once the advisory is\nlive.\n\nThis applies to `AGENTS.md`-following agents as much as to humans. If a task\nasks you to write up a security finding, put the write-up in scratch space and\nsay where it is; do not add it to `devlog/`, `structure/`, or `docs-site/`.\n\n## User-consent actions\n\nSome actions write to the **user's own accounts and identity** rather than to\nthis repository, and an agent must never perform or auto-answer them. One example\nis starring the repository on GitHub, which only comes up when\nan agent is *running* opencodex — not when it is working on this codebase.\n\nThe rule lives in [`AGENTS_INSTALL.md`](./AGENTS_INSTALL.md), which is the file\nan installing or operating agent reads. It was moved out of here because a\ndevelopment-facing file is the wrong place to trigger on it: this file is loaded\nfor every code change, and the consent boundary applies to none of them.\n\nWhat matters for development work: the enforcement is code, not prose —\n[`src/cli/agent-driven.ts`](./src/cli/agent-driven.ts),\n[`src/cli/star-prompt.ts`](./src/cli/star-prompt.ts), and\n[`src/server/management/sidebar-routes.ts`](./src/server/management/sidebar-routes.ts),\ncovered by `tests/server/startup-prompt.test.ts`, `tests/cli/agent-driven.test.ts`, and\n`tests/server/sidebar-routes.test.ts`. If you add another action that spends the user's\nidentity, credits, or reputation, gate it the same way rather than relying on a\nprompt an agent can answer, and document it in `AGENTS_INSTALL.md`.\n\n**Be clear about what that enforcement is and is not.** The management endpoint\nrequires a dashboard session, which stops the casual path — an agent that would\nhave POSTed there because the endpoint existed, and one holding only the admin\ntoken. It is not a technical barrier against a determined local agent: a process\nrunning as the user can mint its own session from the loopback dashboard\nbootstrap, and can skip the proxy entirely by running `gh` itself. Every local\ncredential is equally reachable by both the browser and the agent, so no check\ninside this process can tell them apart. The real boundary is the rule above, and\nit binds you regardless of which mechanism is within reach.\n\n## Commands\n\n```bash\nbun install\nbun run typecheck      # bun x tsc --noEmit (strict)\nbun run test:changed   # import-graph tests against the resolved `dev` merge base\nbun run test           # full tests/ suite (default before review)\nbun run lint:gui       # GUI eslint\nbun run privacy:scan   # credential/privacy scan used by CI\nbun run structure:check # structure/ doc-map, ownership, and invariant-binding gate\nbun run structure:index # regenerate structure/INDEX.md from structure/manifest.json\nbun run build:gui      # Vite GUI build\n```\n\n`skills/ocx/` is the operating reference for the CLI — what an agent reads to *drive* a running\nproxy, as opposed to [`AGENTS_INSTALL.md`](./AGENTS_INSTALL.md) (installing and operating consent)\nor this file (changing the codebase). Its surface map is generated:\n\n```bash\nbun run skill:surface        # regenerate after adding a capability\nbun run skill:surface:check  # what CI asserts\n```\n\n`tests/ci-workflows/skill-ocx.test.ts` fails if the committed map drifts from `src/cli/capabilities.ts`, and\nalso if the hand-written pages name a command the registry does not have. That second check is not\nhypothetical: it caught a documented `ocx request-history` that never existed.\n\nRun the test suite for a change; `bun run test` is the default before a\nnon-trivial PR is marked review-ready or approved. During implementation, use\nfocused files or `bun run test:changed` for faster feedback.\n\nIf a full local run is disproportionately expensive for the task or available\nresources, including contention across concurrent worktrees, run at least the\nfocused regression tests that exercise the changed behavior. This is a scope\nexception, not permission to skip testing or ignore a failing test. Record why\nthe full run was impractical, the exact commands and results, and the coverage\nleft to CI in the PR's Verification section. Never describe an unrun suite as\npassing. Run `bun run typecheck` before review readiness as well.\n\n`bun run test:changed` follows Bun's parsed module graph, so it cannot discover\ndependencies expressed through subprocesses, source files read as data, or\ngolden/derived files. Run those relevant regression files explicitly. If a\nfocused set cannot reliably cover the change, keep the PR in draft until the\nbroader validation is available.\n\nAfter pushing, inspect the required CI for the current PR head. Missing,\nawaiting-approval, skipped, cancelled, or older-head results are not passing\nevidence. Required checks must actually complete successfully before merge.\nThe repository does not install an automatic pre-push validation hook;\n`bun run prepush` remains available as an explicit comprehensive check.\n\nDo not rerun passing checks on unchanged code merely for additional confidence.\n\n## What a green pull request does not tell you\n\nExact-head CI cannot see a defect that exists only in the union of two changes. Each\nbranch is correct at its own head, the merge is not, and the failure lands on whoever\npushes next. One round produced ten of these, including an hour of red `dev`, so the\nclasses below are worth checking before you push rather than after.\n\n### The file-size ratchet has almost no headroom\n\n`tests/fixtures/file-size-baseline.json` records a line cap per file and\n`updateBaseline` uses `Math.min`, so a cap only ever moves **downward**. Raising one is\nnot possible by design, and a cap is not a suggestion you can negotiate with.\n\nAt the time of writing, 39 of the 51 tracked files sit at **exactly** their cap and three\nmore are within five lines. Among them are `src/server/index.ts`, `src/config.ts`,\n`src/server/responses/core.ts`, `gui/src/pages/Models.tsx`, and the large test files\n`tests/codex-integration/codex-catalog.test.ts` and\n`tests/responses/openai-responses-passthrough.test.ts`. Adding one line to any of them\nfails `file-size ratchet: repository` for your branch and for every branch cut from\n`dev` afterwards.\n\nTwo branches can each stay under a cap alone and sum over it together; that is what\nhappened in #4908, #5011 and #5018. The remedy is always a move, never a number: put the\nnew case in a sibling file, byte for byte, and register it in **both**\n`scripts/test-layout/layout.json` (or its domain shard) and a `tests/fixtures/test-layout-expected*.json` shard.\n`d3ca5522db` is the original precedent.\n\nA moved test is not automatically the same test. One case moved out of\n`codex-v2-gate.test.ts` failed in isolation and then failed again in place once unrelated\nblocks moved around it, because its final assertion was reading catalog state earlier\ncases had warmed rather than the contract. If a moved case changes colour, suspect the\ncase before the move.\n\n### Anything exhaustive over a union\n\nA locale catalog, a `satisfies Record<Union, ...>`, a hand-maintained roster, a count in\ngenerated documentation. Adding a member to the union in one branch while another branch\nadds a consumer keyed by it produces a merge that typechecks in neither direction.\n\n`typecheck` precedes every job, so one missing member is not one red suite. It took down\nfourteen checks on `dev` — all four test shards, `docker smoke`, `storage policy`,\n`api usage` and all three `npm-global` smokes — when a closed translation namespace still\nlisted nine locales after a tenth had landed.\n\nCounts drift the same way and more quietly, because both sides write a plausible number.\nTwo branches each added one CLI capability and each wrote `47`; the merged truth was 48.\nTwo each added one provider preset and each wrote `94`; the registry had 95. Prefer\nderiving a count or a member list from the thing it describes over restating it.\n\n## Minimal containers and agent sandboxes\n\nFresh dev containers and agent sandboxes (Cursor Cloud, devcontainers, CI\nimages) often ship Node but not Bun. Install it first:\n\n```bash\ncurl -fsSL https://bun.sh/install | bash   # installs ~/.bun/bin/bun\nexport PATH=\"$HOME/.bun/bin:$PATH\"\nbun install && (cd gui && bun install)\n```\n\nRun the proxy with `bun run src/cli/index.ts start --port <port>`. `/healthz`\nreports status, `/` serves the dashboard, and the management API requires the\nadmin token the server writes to `$OPENCODEX_HOME/admin-api-token` at startup.\n\n`bun run test` has five known environment-only failures in such containers.\nThey are not regressions; do not re-investigate them:\n\n- `service diagnostics > status summary exposes the service log path`,\n  `CLI subcommand help > status prints diagnostics without starting the proxy`,\n  and `CLI subcommand help > invalid service and codex-shim usage include\n  remove alias` require a running systemd init; in a container PID 1 is\n  typically `tini` or another minimal init, so service commands report\n  \"systemd not found\".\n- `package tree integrity > an in-place rewrite of the same byte length is\n  still a replacement` and `Codex Log Guard inspection > repeat inspection is\n  memoized and invalidated by a write` rely on filesystem mtime granularity\n  that some container filesystems do not provide.\n\nEverything else passes (15480 pass / 16 skip / 5 fail as of 2.35.0).\n\n## Issues and pull requests (agents)\n\nAgent-created issues and PRs must use the repository templates. The gates\nbelow enforce them, so a freeform or mismatched submission is rejected rather\nthan nudged.\n\n- **Creating an issue:** open it through the template chooser and use the\n  matching form in `.github/ISSUE_TEMPLATE/` — `bug_report.yml` (Bug report),\n  `feature_request.yml` (Feature proposal), `documentation.yml`\n  (Documentation), or `provider_compatibility.yml` (Provider or API\n  compatibility). Keep the form's section headings exactly as generated;\n  `enforce-issue-quality` validates the headings and closes untemplated or\n  mislabeled issues (`.github/ISSUE_TEMPLATE/config.yml` disables blank\n  issues, so there is no freeform fallback).\n- **Opening a pull request:** fill every section of\n  `.github/PULL_REQUEST_TEMPLATE.md` (Summary, Verification, Checklist).\n  `enforce-target` rejects empty, thin, or malformed descriptions. If the PR\n  changes files under `gui/`, include a screenshot of the UI change in the\n  description; the check re-runs on description edits until the screenshot is\n  present. Drag the image into the description editor rather than committing it:\n  an image on your branch rides the squash merge into `dev`. Maintainers\n  uploading from the command line use the `pr-assets` branch and link by commit\n  SHA. Never commit screenshot evidence to the PR branch — the squash merge carries\n  it into `dev`, which is how `docs/pr-assets/` and its siblings grew until\n  they were deleted; `tests/ci-workflows/repo-hygiene.test.ts` now rejects\n  those folders. When the PR resolves an issue, add\n  `Closes #<number>` to link it. GitHub auto-closes the linked issue only\n  when the PR merges into the default branch (`main`); PRs here target\n  `dev`, so close the issue manually once the change is on `dev`.\n- **Landing another author's work:** reimplementing, superseding, carrying, or\n  rebasing someone else's pull request requires a `Co-authored-by` trailer\n  naming that author, in the description or in a branch commit so it survives\n  the squash. Saying it in prose is not equivalent — the trailer is what GitHub\n  reads for the contributor graph, and a sentence in a commit body is read by\n  nothing. This repository did it both ways for months: `53c09a247` says \"Clean\n  reimplementation of #3193\" and names the author in a trailer, `5734a1caf` says\n  \"Reimplements #2797 by @rrmlima\" and names nobody, so that contribution is\n  invisible on its author's profile. The 27 landings already in that state are\n  recorded in [`CREDITS.md`](./CREDITS.md); `missing_coauthor_credit` in\n  `.github/scripts/pr-carry-attribution.cjs` is why the list should not grow.\n\n## CI policy (maintainer decision — do not change)\n\n- Pull requests run **only the fast Ubuntu subset**.\n- Full cross-platform (macOS/Windows) CI runs **only** via manual\n  `workflow_dispatch` and on push to `main`/`preview` (release lines).\n- **Never** add a `dev` push trigger, or `pull_request` runs for macOS/Windows\n  jobs, to any workflow. PRs that change this policy will be closed.\n\n## Branch policy\n\n- `dev` — the single integration branch and the target for every pull request.\n- `main` — release branch. It only moves by maintainer-controlled promotion\n  from `dev` (releases, docs deploys). Do not open feature PRs against `main`.\n- `preview` — prerelease train (`x.y.z-preview.*` versions).\n\nBun-native TypeScript on `dev` is the only runtime line. If native code\nreturns, the expectation is an incremental module (for example Rust via N-API)\nlanding on `dev`, not a second full-runtime branch.\n\nStacked child pull requests that target another **open** PR's head branch are\nan intentional review workflow, not an alternate integration line. The\n**`enforce-target`** check skips the wrong-base gate for those children; after\nthe parent lands or closes, retarget the child to `dev`.\n\nRebase pull requests are welcome. Bringing a stale branch onto the current head\nis ordinary maintenance — open it as a normal pull request and name the source\ncommits in the description.\n\nThe **`enforce-target`** CI check rejects pull requests whose head\nancestry sits on the **`main`** tip while far behind **`dev`**, and rejects\nempty, thin, or malformed descriptions. If changed paths include files under\n`gui/`, include a screenshot of the UI change in the description; the check\nre-runs on description edits until the screenshot is present. Drag the image\ninto the description editor rather than committing it, or, when uploading from\nthe command line, use the `pr-assets` branch and link by commit SHA.\nContributor PRs (authors without repository push permission) open in draft and\nstay there until a four-box review-readiness checklist in the description is\ncomplete: required local validation passed with its scope documented, branch\non the latest `dev` commit, all correct Codex and CodeRabbit findings fixed,\nand the ready-for-review confirmation. When all\nfour boxes are ticked the gate marks the PR ready and notifies the maintainers\nlisted in `MAINTAINERS.md` (excluding the author). Completion is bound to the\nexact commit the PR head pointed at: if new commits are pushed afterwards, the\ngate moves the PR back to draft, resets the checklist and the notification,\nand asks the author to test and tick the boxes again against the latest code.\nBefore a completion is accepted, the gate verifies the checklist claims it\ncan check itself: the branch must be on the latest `dev` commit or at most\n10 commits behind it, and Codex/CodeRabbit findings must be resolved. The\nlocal-validation box is an author attestation only — fork contributors cannot start\nrepository CI; a maintainer has to — so the gate never disproves it; a new\npush still resets every box. A disproved claim unticks the matching box and\nkeeps the PR a draft.\nAuthors with repository push permission skip the ancestry heuristic only. As with approval requirements in\n[`MAINTAINERS.md`](./MAINTAINERS.md), the ancestry heuristic is a CI check\nrather than a branch rule. The branches themselves are protected: `dev`,\n`main`, and `preview` each require a pull request and block force-pushes and deletion.\nFor `dev` only, a current maintainer with GitHub `maintain` or `admin` access may\nexplicitly integrate through a PR without another maintainer approval, including\ntheir own PR, under the policy in `MAINTAINERS.md`. Record the decision and exact-head\nCI evidence; keep outstanding maintainer objections and security review separate.\nThe bypass is PR-only, so a direct push to `dev` remains rejected regardless of\nlocal hook settings. Contributor review and `main`/`preview` rules remain unchanged.\n\n[`MAINTAINERS.md`](./MAINTAINERS.md) is authoritative for review and merge\npolicy (approvals, CI requirements, security review, promotion). This file\nsummarizes; it never overrides it.\n\n## Review guidelines\n\nThese rules apply to all code reviews on this repository, including automated\nreviewers (Codex, CodeRabbit).\n\n- **Language:** always review in English, regardless of the PR or issue\n  language. Be detailed and specific: name the file and line, describe the\n  concrete failure mode, and suggest a fix. Avoid vague or purely stylistic\n  commentary.\n- **Branch targeting:** flag any pull request that does not target `dev`\n  (releases and maintainer promotions are the only exceptions).\n- **Security boundary (highest priority):** changes touching authentication,\n  credential/token handling, OAuth flows, GitHub Actions workflows, release\n  automation (`scripts/release.ts`, `.github/workflows/release.yml`), or\n  dependency installation require explicit security review per\n  `MAINTAINERS.md`. Treat token logging/serialization, secret exposure,\n  workflow permission escalation, and mutable third-party action refs as\n  release blockers.\n- **Runtime constraints:** the proxy is Bun-native. Flag Node-only APIs,\n  assumptions about a compile step, or code paths that break `bun run\n  typecheck` / `bun run test`.\n- **Tests:** behavior changes in `src/` need a focused regression test near\n  the existing tests for that subsystem. During implementation, run the relevant\n  focused files and use `bun run test:changed` for import-connected coverage as\n  described above. Full-suite validation is the default before review readiness;\n  the documented resource exception still requires focused regression tests.\n- **Docs sync:** user-facing behavior changes should update `docs-site/` (and\n  keep translated locales from contradicting the English source).\n- **Privacy:** `bun run privacy:scan` must stay green; never introduce logging\n  of request bodies, API keys, or account identifiers.\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.claude-plugin/plugin.json)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nGuidance for AI agents (and humans) working on or reviewing this repository.\n\n## What this project is\n\nopencodex (`ocx`) is a universal provider proxy for OpenAI Codex and Claude Code:\none local proxy that lets Codex CLI/App/SDK and Claude Code use many LLM\nproviders (Claude, Gemini, Grok, DeepSeek, Ollama, and more). The runtime is\nBun-native TypeScript with no separate server compile step.\n\n## Repository layout\n\n- `src/` — proxy runtime: routing, provider adapters, config, management API.\n- `tests/` — Bun tests in domain directories that mirror `src/`\n  (`tests/<domain>/*.test.ts`; `providers/` and `adapters/` have one more\n  level for the larger vendors). The explicit map is\n  `scripts/test-layout/layout.json` plus its `explicitShards` sibling maps, with regex seeds and migration state in\n  `scripts/test-layout/seeds.json`; `tests/test-layout.test.ts` enforces that every file resolves to a\n  domain and sits in it, and only the two layout guards live at the root.\n  Shared helpers in `tests/helpers/`, fixtures in `tests/fixtures/`, broader\n  scenarios in `tests/e2e-style/`. Source-oracle tests resolve the repository\n  through `tests/helpers/repo-root.ts` (`repoRoot()`, `repoPath()`,\n  `helperPath()`, `fixturePath()`), never `import.meta.dir + \"/..\"`. A new\n  test file lands in its domain directory and needs an entry in both\n  `layout.json` `explicit` (or its domain shard) and a `tests/fixtures/test-layout-expected*.json` shard\n  (`tests/test-layout-tooling.test.ts` names the missing one); the regex\n  seeds in `seeds.json` place a conventionally named file until then.\n  History: `devlog/_fin/260905_test_modularization_and_windows/`.\n- `gui/` — React + Vite dashboard; packaged output is served from `gui/dist`.\n- `app/` — native macOS WidgetKit extension bundled into the Tauri desktop app;\n  `MenuBarCore` is its snapshot model/formatting layer. Its tests are\n  executables, not XCTest bundles — Command Line Tools ships neither a usable\n  XCTest module nor the swift-testing runtime.\n- `docs-site/` — public docs (Astro + Starlight), deployed to GitHub Pages.\n- `go/` — retired Go native-runtime experiment; kept only where the TypeScript\n  runtime still references it. New work does not go here.\n- `structure/` — maintainer invariants and architecture notes; read before\n  changing shared subsystems. [`structure/INDEX.md`](./structure/INDEX.md) is the\n  reading order and the source-ownership table, and\n  [`structure/AGENTS.md`](./structure/AGENTS.md) holds the rules for changing\n  anything in there. Ownership is not advisory: changing an owned source area\n  obliges the same change to update its doc, and `bun run structure:check`\n  (wired into the suite by `tests/ci-workflows/structure-ssot.test.ts`) fails on a\n  doc that names a path this tree no longer has, on an invariant whose test is\n  gone, and on a new `src/` area nobody claimed.\n- `scripts/` — release and maintenance tooling; `scripts/release.ts` is the\n  release authority.\n- `desktop/` — Tauri v2 desktop shell, bootstrap UI, and compiled proxy sidecar preparation.\n- `devlog/` — planning and investigation notes, tracked in this repository. See\n  \"The `devlog` directory\" below for what may and may not go there.\n\nRead the nearest nested `AGENTS.md` before changing files in a scoped\ndirectory (`src/`, `gui/`, `docs-site/`, `scripts/`, `.github/`).\n\n## Optional subsystems stay off the core path\n\n`src/lab/` (Compatibility Lab) is opt-in. A user who configures one provider and\none model — no routing profile, no Lab — must execute no Lab code and start no\nLab timer.\n\nThree files carry every such user's request path and must not reach `src/lab/`,\ndirectly or transitively:\n\n- `src/router.ts`\n- `src/server/lifecycle.ts`\n- `src/server/responses/core.ts`\n\n`tests/lab/core-lab-boundary.test.ts` enforces this by walking the runtime import\ngraph and printing the offending chain on failure. It is not a style rule: the\noriginal violation hid in a six-hop chain\n(`assemble → quota → auth-api → native-main-admission → lifecycle → lab`) where\nno single file looked wrong, and it pulled ~69 Lab modules into every install.\n\nAn optional subsystem registers into a core-owned slot at activation instead of\nbeing imported. The existing seams are `src/server/passive-route-linker.ts`,\n`src/routing/compatibility/provider-slot.ts`, and\n`src/lib/optional-shutdown-hooks.ts`.\n\n`src/server/index.ts` is deliberately exempt: a composition root is supposed to\nknow which optional subsystems exist. Its obligation is the gate, not the import\n— activation must stay behind `labActivationRequired`, and it must stay\nsynchronous. Everything between `Bun.serve` and the return of `startServer` runs\nin one synchronous turn, which is what guarantees a policy route can never be\nevaluated before its evidence provider is registered. The synchronous\nsubagent-fallback chain has nowhere to await, so an `await` added before the\nactivation block would silently reroute subagents to a different model than the\noperator configured.\n\nThat one is enforced too, in the same file: a scan reads the window between the\n`Bun.serve` call and the `labActivationRequired` check and fails on any `await`\nthat would suspend `startServer` itself, plus on `startServer` being declared\n`async`. It has to ignore comments, string bodies, and nested functions to be\nusable, because the window legitimately contains three awaits inside the\n`server.stop` closure and two comments that mention the word. Until it existed,\nthis paragraph was the only thing holding the guarantee.\n\nDesign and audit history: `devlog/_fin/260814_lab_core_decoupling/`.\n\n## The `devlog` directory\n\nPlanning notes, triage matrices, and investigation artifacts live in `devlog/`,\ntracked like any other documentation. There is no submodule and no private\nmirror. It was a private submodule until the pointer churn outgrew its value:\n1723 commits touched the gitlink, and `dev`, `preview`, and `main` each carried a\ndifferent pointer, so every branch move and promotion dragged a diff.\n\n- `devlog/_plan/` — units still open, one directory per unit, decade-numbered\n  docs.\n- `devlog/_fin/` — closed units, moved here once a terminal outcome is recorded.\n  A `_fin` unit is a record of work already visible in public git history.\n- `devlog/_chase/` — external reference material for parity comparisons.\n  Reference *clones* are gitignored: they are third-party source carrying their\n  own licenses and have no business in this repository's history.\n\nNothing in the build, typecheck, or test path reads from `devlog/`, so a\ncontributor who ignores it entirely still passes every gate. `privacy:scan` does\nread it — that is deliberate, and it is what makes a public devlog safe rather\nthan merely visible.\n\nTwo mechanical guards in `tests/ci-workflows/repo-hygiene.test.ts` back this up: no `160000`\ngitlink may be tracked anywhere, and neither the vendored reference clones nor\nthe security triage excised before publication may reappear in the index. Both\nwere driven red once to prove they are not vacuous. The gitlink assertion exists\nbecause a gitlink in a tree CI does not initialize breaks `actions/checkout` for\nevery contributor, which happened twice.\n\n## Security working notes\n\n**Security work is done in scratch space, never in a tracked directory.** That\nincludes unreleased findings, severity assessments, draft advisories, exploit\nor bypass reasoning, reproduction steps for an unfixed defect, and\npre-disclosure patch plans.\n\nUse `.tmp/` in the working tree (already gitignored) or a `mktemp -d` path.\n`devlog/` is **not** an acceptable location — it is a public directory in a\npublic repository, so anything committed there is disclosed the moment it is\npushed, and the history is not practical to purge afterwards. A private\nrepository is not acceptable either: it gets cloned across machines and CI and\noutlives the embargo.\n\n**This binds maintainers exactly as it binds contributors and agents.** The rule\nhas been violated by maintainer-authored triage before: two units of open\nsecurity review accumulated under `devlog/_plan/` and had to be excised before\nthis directory could be published. Seniority is not an exemption, and \"it is\nonly in the private half\" is no longer a thing that exists.\n\nThe test to apply before writing a security note into `devlog/`: **is there\nalready a public diff that reveals this weakness?** If the fix has shipped, the\nwriteup discloses nothing new and belongs in `_fin/`. If it has not, the note is\npre-disclosure material and goes to scratch. That distinction is why closed\nhardening records stay in the tree while open triage does not.\n\nOnly the published outcome reaches a repository — the fix itself, its\nregression test, the release note, the advisory once it is public. Draft the\nadvisory in scratch space and delete the scratch directory once the advisory is\nlive.\n\nThis applies to `AGENTS.md`-following agents as much as to humans. If a task\nasks you to write up a security finding, put the write-up in scratch space and\nsay where it is; do not add it to `devlog/`, `structure/`, or `docs-site/`.\n\n## User-consent actions\n\nSome actions write to the **user's own accounts and identity** rather than to\nthis repository, and an agent must never perform or auto-answer them. One example\nis starring the repository on GitHub, which only comes up when\nan agent is *running* opencodex — not when it is working on this codebase.\n\nThe rule lives in [`AGENTS_INSTALL.md`](./AGENTS_INSTALL.md), which is the file\nan installing or operating agent reads. It was moved out of here because a\ndevelopment-facing file is the wrong place to trigger on it: this file is loaded\nfor every code change, and the consent boundary applies to none of them.\n\nWhat matters for development work: the enforcement is code, not prose —\n[`src/cli/agent-driven.ts`](./src/cli/agent-driven.ts),\n[`src/cli/star-prompt.ts`](./src/cli/star-prompt.ts), and\n[`src/server/management/sidebar-routes.ts`](./src/server/management/sidebar-routes.ts),\ncovered by `tests/server/startup-prompt.test.ts`, `tests/cli/agent-driven.test.ts`, and\n`tests/server/sidebar-routes.test.ts`. If you add another action that spends the user's\nidentity, credits, or reputation, gate it the same way rather than relying on a\nprompt an agent can answer, and document it in `AGENTS_INSTALL.md`.\n\n**Be clear about what that enforcement is and is not.** The management endpoint\nrequires a dashboard session, which stops the casual path — an agent that would\nhave POSTed there because the endpoint existed, and one holding only the admin\ntoken. It is not a technical barrier against a determined local agent: a process\nrunning as the user can mint its own session from the loopback dashboard\nbootstrap, and can skip the proxy entirely by running `gh` itself. Every local\ncredential is equally reachable by both the browser and the agent, so no check\ninside this process can tell them apart. The real boundary is the rule above, and\nit binds you regardless of which mechanism is within reach.\n\n## Commands\n\n```bash\nbun install\nbun run typecheck      # bun x tsc --noEmit (strict)\nbun run test:changed   # import-graph tests against the resolved `dev` merge base\nbun run test           # full tests/ suite (default before review)\nbun run lint:gui       # GUI eslint\nbun run privacy:scan   # credential/privacy scan used by CI\nbun run structure:check # structure/ doc-map, ownership, and invariant-binding gate\nbun run structure:index # regenerate structure/INDEX.md from structure/manifest.json\nbun run build:gui      # Vite GUI build\n```\n\n`skills/ocx/` is the operating reference for the CLI — what an agent reads to *drive* a running\nproxy, as opposed to [`AGENTS_INSTALL.md`](./AGENTS_INSTALL.md) (installing and operating consent)\nor this file (changing the codebase). Its surface map is generated:\n\n```bash\nbun run skill:surface        # regenerate after adding a capability\nbun run skill:surface:check  # what CI asserts\n```\n\n`tests/ci-workflows/skill-ocx.test.ts` fails if the committed map drifts from `src/cli/capabilities.ts`, and\nalso if the hand-written pages name a command the registry does not have. That second check is not\nhypothetical: it caught a documented `ocx request-history` that never existed.\n\nRun the test suite for a change; `bun run test` is the default before a\nnon-trivial PR is marked review-ready or approved. During implementation, use\nfocused files or `bun run test:changed` for faster feedback.\n\nIf a full local run is disproportionately expensive for the task or available\nresources, including contention across concurrent worktrees, run at least the\nfocused regression tests that exercise the changed behavior. This is a scope\nexception, not permission to skip testing or ignore a failing test. Record why\nthe full run was impractical, the exact commands and results, and the coverage\nleft to CI in the PR's Verification section. Never describe an unrun suite as\npassing. Run `bun run typecheck` before review readiness as well.\n\n`bun run test:changed` follows Bun's parsed module graph, so it cannot discover\ndependencies expressed through subprocesses, source files read as data, or\ngolden/derived files. Run those relevant regression files explicitly. If a\nfocused set cannot reliably cover the change, keep the PR in draft until the\nbroader validation is available.\n\nAfter pushing, inspect the required CI for the current PR head. Missing,\nawaiting-approval, skipped, cancelled, or older-head results are not passing\nevidence. Required checks must actually complete successfully before merge.\nThe repository does not install an automatic pre-push validation hook;\n`bun run prepush` remains available as an explicit comprehensive check.\n\nDo not rerun passing checks on unchanged code merely for additional confidence.\n\n## What a green pull request does not tell you\n\nExact-head CI cannot see a defect that exists only in the union of two changes. Each\nbranch is correct at its own head, the merge is not, and the failure lands on whoever\npushes next. One round produced ten of these, including an hour of red `dev`, so the\nclasses below are worth checking before you push rather than after.\n\n### The file-size ratchet has almost no headroom\n\n`tests/fixtures/file-size-baseline.json` records a line cap per file and\n`updateBaseline` uses `Math.min`, so a cap only ever moves **downward**. Raising one is\nnot possible by design, and a cap is not a suggestion you can negotiate with.\n\nAt the time of writing, 39 of the 51 tracked files sit at **exactly** their cap and three\nmore are within five lines. Among them are `src/server/index.ts`, `src/config.ts`,\n`src/server/responses/core.ts`, `gui/src/pages/Models.tsx`, and the large test files\n`tests/codex-integration/codex-catalog.test.ts` and\n`tests/responses/openai-responses-passthrough.test.ts`. Adding one line to any of them\nfails `file-size ratchet: repository` for your branch and for every branch cut from\n`dev` afterwards.\n\nTwo branches can each stay under a cap alone and sum over it together; that is what\nhappened in #4908, #5011 and #5018. The remedy is always a move, never a number: put the\nnew case in a sibling file, byte for byte, and register it in **both**\n`scripts/test-layout/layout.json` (or its domain shard) and a `tests/fixtures/test-layout-expected*.json` shard.\n`d3ca5522db` is the original precedent.\n\nA moved test is not automatically the same test. One case moved out of\n`codex-v2-gate.test.ts` failed in isolation and then failed again in place once unrelated\nblocks moved around it, because its final assertion was reading catalog state earlier\ncases had warmed rather than the contract. If a moved case changes colour, suspect the\ncase before the move.\n\n### Anything exhaustive over a union\n\nA locale catalog, a `satisfies Record<Union, ...>`, a hand-maintained roster, a count in\ngenerated documentation. Adding a member to the union in one branch while another branch\nadds a consumer keyed by it produces a merge that typechecks in neither direction.\n\n`typecheck` precedes every job, so one missing member is not one red suite. It took down\nfourteen checks on `dev` — all four test shards, `docker smoke`, `storage policy`,\n`api usage` and all three `npm-global` smokes — when a closed translation namespace still\nlisted nine locales after a tenth had landed.\n\nCounts drift the same way and more quietly, because both sides write a plausible number.\nTwo branches each added one CLI capability and each wrote `47`; the merged truth was 48.\nTwo each added one provider preset and each wrote `94`; the registry had 95. Prefer\nderiving a count or a member list from the thing it describes over restating it.\n\n## Minimal containers and agent sandboxes\n\nFresh dev containers and agent sandboxes (Cursor Cloud, devcontainers, CI\nimages) often ship Node but not Bun. Install it first:\n\n```bash\ncurl -fsSL https://bun.sh/install | bash   # installs ~/.bun/bin/bun\nexport PATH=\"$HOME/.bun/bin:$PATH\"\nbun install && (cd gui && bun install)\n```\n\nRun the proxy with `bun run src/cli/index.ts start --port <port>`. `/healthz`\nreports status, `/` serves the dashboard, and the management API requires the\nadmin token the server writes to `$OPENCODEX_HOME/admin-api-token` at startup.\n\n`bun run test` has five known environment-only failures in such containers.\nThey are not regressions; do not re-investigate them:\n\n- `service diagnostics > status summary exposes the service log path`,\n  `CLI subcommand help > status prints diagnostics without starting the proxy`,\n  and `CLI subcommand help > invalid service and codex-shim usage include\n  remove alias` require a running systemd init; in a container PID 1 is\n  typically `tini` or another minimal init, so service commands report\n  \"systemd not found\".\n- `package tree integrity > an in-place rewrite of the same byte length is\n  still a replacement` and `Codex Log Guard inspection > repeat inspection is\n  memoized and invalidated by a write` rely on filesystem mtime granularity\n  that some container filesystems do not provide.\n\nEverything else passes (15480 pass / 16 skip / 5 fail as of 2.35.0).\n\n## Issues and pull requests (agents)\n\nAgent-created issues and PRs must use the repository templates. The gates\nbelow enforce them, so a freeform or mismatched submission is rejected rather\nthan nudged.\n\n- **Creating an issue:** open it through the template chooser and use the\n  matching form in `.github/ISSUE_TEMPLATE/` — `bug_report.yml` (Bug report),\n  `feature_request.yml` (Feature proposal), `documentation.yml`\n  (Documentation), or `provider_compatibility.yml` (Provider or API\n  compatibility). Keep the form's section headings exactly as generated;\n  `enforce-issue-quality` validates the headings and closes untemplated or\n  mislabeled issues (`.github/ISSUE_TEMPLATE/config.yml` disables blank\n  issues, so there is no freeform fallback).\n- **Opening a pull request:** fill every section of\n  `.github/PULL_REQUEST_TEMPLATE.md` (Summary, Verification, Checklist).\n  `enforce-target` rejects empty, thin, or malformed descriptions. If the PR\n  changes files under `gui/`, include a screenshot of the UI change in the\n  description; the check re-runs on description edits until the screenshot is\n  present. Drag the image into the description editor rather than committing it:\n  an image on your branch rides the squash merge into `dev`. Maintainers\n  uploading from the command line use the `pr-assets` branch and link by commit\n  SHA. Never commit screenshot evidence to the PR branch — the squash merge carries\n  it into `dev`, which is how `docs/pr-assets/` and its siblings grew until\n  they were deleted; `tests/ci-workflows/repo-hygiene.test.ts` now rejects\n  those folders. When the PR resolves an issue, add\n  `Closes #<number>` to link it. GitHub auto-closes the linked issue only\n  when the PR merges into the default branch (`main`); PRs here target\n  `dev`, so close the issue manually once the change is on `dev`.\n- **Landing another author's work:** reimplementing, superseding, carrying, or\n  rebasing someone else's pull request requires a `Co-authored-by` trailer\n  naming that author, in the description or in a branch commit so it survives\n  the squash. Saying it in prose is not equivalent — the trailer is what GitHub\n  reads for the contributor graph, and a sentence in a commit body is read by\n  nothing. This repository did it both ways for months: `53c09a247` says \"Clean\n  reimplementation of #3193\" and names the author in a trailer, `5734a1caf` says\n  \"Reimplements #2797 by @rrmlima\" and names nobody, so that contribution is\n  invisible on its author's profile. The 27 landings already in that state are\n  recorded in [`CREDITS.md`](./CREDITS.md); `missing_coauthor_credit` in\n  `.github/scripts/pr-carry-attribution.cjs` is why the list should not grow.\n\n## CI policy (maintainer decision — do not change)\n\n- Pull requests run **only the fast Ubuntu subset**.\n- Full cross-platform (macOS/Windows) CI runs **only** via manual\n  `workflow_dispatch` and on push to `main`/`preview` (release lines).\n- **Never** add a `dev` push trigger, or `pull_request` runs for macOS/Windows\n  jobs, to any workflow. PRs that change this policy will be closed.\n\n## Branch policy\n\n- `dev` — the single integration branch and the target for every pull request.\n- `main` — release branch. It only moves by maintainer-controlled promotion\n  from `dev` (releases, docs deploys). Do not open feature PRs against `main`.\n- `preview` — prerelease train (`x.y.z-preview.*` versions).\n\nBun-native TypeScript on `dev` is the only runtime line. If native code\nreturns, the expectation is an incremental module (for example Rust via N-API)\nlanding on `dev`, not a second full-runtime branch.\n\nStacked child pull requests that target another **open** PR's head branch are\nan intentional review workflow, not an alternate integration line. The\n**`enforce-target`** check skips the wrong-base gate for those children; after\nthe parent lands or closes, retarget the child to `dev`.\n\nRebase pull requests are welcome. Bringing a stale branch onto the current head\nis ordinary maintenance — open it as a normal pull request and name the source\ncommits in the description.\n\nThe **`enforce-target`** CI check rejects pull requests whose head\nancestry sits on the **`main`** tip while far behind **`dev`**, and rejects\nempty, thin, or malformed descriptions. If changed paths include files under\n`gui/`, include a screenshot of the UI change in the description; the check\nre-runs on description edits until the screenshot is present. Drag the image\ninto the description editor rather than committing it, or, when uploading from\nthe command line, use the `pr-assets` branch and link by commit SHA.\nContributor PRs (authors without repository push permission) open in draft and\nstay there until a four-box review-readiness checklist in the description is\ncomplete: required local validation passed with its scope documented, branch\non the latest `dev` commit, all correct Codex and CodeRabbit findings fixed,\nand the ready-for-review confirmation. When all\nfour boxes are ticked the gate marks the PR ready and notifies the maintainers\nlisted in `MAINTAINERS.md` (excluding the author). Completion is bound to the\nexact commit the PR head pointed at: if new commits are pushed afterwards, the\ngate moves the PR back to draft, resets the checklist and the notification,\nand asks the author to test and tick the boxes again against the latest code.\nBefore a completion is accepted, the gate verifies the checklist claims it\ncan check itself: the branch must be on the latest `dev` commit or at most\n10 commits behind it, and Codex/CodeRabbit findings must be resolved. The\nlocal-validation box is an author attestation only — fork contributors cannot start\nrepository CI; a maintainer has to — so the gate never disproves it; a new\npush still resets every box. A disproved claim unticks the matching box and\nkeeps the PR a draft.\nAuthors with repository push permission skip the ancestry heuristic only. As with approval requirements in\n[`MAINTAINERS.md`](./MAINTAINERS.md), the ancestry heuristic is a CI check\nrather than a branch rule. The branches themselves are protected: `dev`,\n`main`, and `preview` each require a pull request and block force-pushes and deletion.\nFor `dev` only, a current maintainer with GitHub `maintain` or `admin` access may\nexplicitly integrate through a PR without another maintainer approval, including\ntheir own PR, under the policy in `MAINTAINERS.md`. Record the decision and exact-head\nCI evidence; keep outstanding maintainer objections and security review separate.\nThe bypass is PR-only, so a direct push to `dev` remains rejected regardless of\nlocal hook settings. Contributor review and `main`/`preview` rules remain unchanged.\n\n[`MAINTAINERS.md`](./MAINTAINERS.md) is authoritative for review and merge\npolicy (approvals, CI requirements, security review, promotion). This file\nsummarizes; it never overrides it.\n\n## Review guidelines\n\nThese rules apply to all code reviews on this repository, including automated\nreviewers (Codex, CodeRabbit).\n\n- **Language:** always review in English, regardless of the PR or issue\n  language. Be detailed and specific: name the file and line, describe the\n  concrete failure mode, and suggest a fix. Avoid vague or purely stylistic\n  commentary.\n- **Branch targeting:** flag any pull request that does not target `dev`\n  (releases and maintainer promotions are the only exceptions).\n- **Security boundary (highest priority):** changes touching authentication,\n  credential/token handling, OAuth flows, GitHub Actions workflows, release\n  automation (`scripts/release.ts`, `.github/workflows/release.yml`), or\n  dependency installation require explicit security review per\n  `MAINTAINERS.md`. Treat token logging/serialization, secret exposure,\n  workflow permission escalation, and mutable third-party action refs as\n  release blockers.\n- **Runtime constraints:** the proxy is Bun-native. Flag Node-only APIs,\n  assumptions about a compile step, or code paths that break `bun run\n  typecheck` / `bun run test`.\n- **Tests:** behavior changes in `src/` need a focused regression test near\n  the existing tests for that subsystem. During implementation, run the relevant\n  focused files and use `bun run test:changed` for import-connected coverage as\n  described above. Full-suite validation is the default before review readiness;\n  the documented resource exception still requires focused regression tests.\n- **Docs sync:** user-facing behavior changes should update `docs-site/` (and\n  keep translated locales from contradicting the English source).\n- **Privacy:** `bun run privacy:scan` must stay green; never introduce logging\n  of request bodies, API keys, or account identifiers.\n","isInternal":false,"tokens":6234,"sizeBytes":26851},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.claude-plugin/marketplace.json","title":"Claude Plugin Marketplace Catalog","category":"marketplace","format":"json","content":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.claude-plugin/marketplace.json)","tokens":300,"sizeBytes":198},{"name":"plugin.json","path":".claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.claude-plugin/plugin.json","title":"claude-plugin Plugin Manifest","category":"plugin-manifest","format":"json","content":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.claude-plugin/plugin.json)","tokens":300,"sizeBytes":184},{"name":"mcp.json","path":".cursor/mcp.json","rawUrl":"https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.cursor/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.cursor/mcp.json)","tokens":300,"sizeBytes":177}],"systemPromptSnippet":"<yakaai_skills repo=\"lidge-jun/opencodex\">\n<!-- File: AGENTS.md (Tokens: ~6234 | Category: root-instruction) -->\n# AGENTS.md\n\nGuidance for AI agents (and humans) working on or reviewing this repository.\n\n## What this project is\n\nopencodex (`ocx`) is a universal provider proxy for OpenAI Codex and Claude Code:\none local proxy that lets Codex CLI/App/SDK and Claude Code use many LLM\nproviders (Claude, Gemini, Grok, DeepSeek, Ollama, and more). The runtime is\nBun-native TypeScript with no separate server compile step.\n\n## Repository layout\n\n- `src/` — proxy runtime: routing, provider adapters, config, management API.\n- `tests/` — Bun tests in domain directories that mirror `src/`\n  (`tests/<domain>/*.test.ts`; `providers/` and `adapters/` have one more\n  level for the larger vendors). The explicit map is\n  `scripts/test-layout/layout.json` plus its `explicitShards` sibling maps, with regex seeds and migration state in\n  `scripts/test-layout/seeds.json`; `tests/test-layout.test.ts` enforces that every file resolves to a\n  domain and sits in it, and only the two layout guards live at the root.\n  Shared helpers in `tests/helpers/`, fixtures in `tests/fixtures/`, broader\n  scenarios in `tests/e2e-style/`. Source-oracle tests resolve the repository\n  through `tests/helpers/repo-root.ts` (`repoRoot()`, `repoPath()`,\n  `helperPath()`, `fixturePath()`), never `import.meta.dir + \"/..\"`. A new\n  test file lands in its domain directory and needs an entry in both\n  `layout.json` `explicit` (or its domain shard) and a `tests/fixtures/test-layout-expected*.json` shard\n  (`tests/test-layout-tooling.test.ts` names the missing one); the regex\n  seeds in `seeds.json` place a conventionally named file until then.\n  History: `devlog/_fin/260905_test_modularization_and_windows/`.\n- `gui/` — React + Vite dashboard; packaged output is served from `gui/dist`.\n- `app/` — native macOS WidgetKit extension bundled into the Tauri desktop app;\n  `MenuBarCore` is its snapshot model/formatting layer. Its tests are\n  executables, not XCTest bundles — Command Line Tools ships neither a usable\n  XCTest module nor the swift-testing runtime.\n- `docs-site/` — public docs (Astro + Starlight), deployed to GitHub Pages.\n- `go/` — retired Go native-runtime experiment; kept only where the TypeScript\n  runtime still references it. New work does not go here.\n- `structure/` — maintainer invariants and architecture notes; read before\n  changing shared subsystems. [`structure/INDEX.md`](./structure/INDEX.md) is the\n  reading order and the source-ownership table, and\n  [`structure/AGENTS.md`](./structure/AGENTS.md) holds the rules for changing\n  anything in there. Ownership is not advisory: changing an owned source area\n  obliges the same change to update its doc, and `bun run structure:check`\n  (wired into the suite by `tests/ci-workflows/structure-ssot.test.ts`) fails on a\n  doc that names a path this tree no longer has, on an invariant whose test is\n  gone, and on a new `src/` area nobody claimed.\n- `scripts/` — release and maintenance tooling; `scripts/release.ts` is the\n  release authority.\n- `desktop/` — Tauri v2 desktop shell, bootstrap UI, and compiled proxy sidecar preparation.\n- `devlog/` — planning and investigation notes, tracked in this repository. See\n  \"The `devlog` directory\" below for what may and may not go there.\n\nRead the nearest nested `AGENTS.md` before changing files in a scoped\ndirectory (`src/`, `gui/`, `docs-site/`, `scripts/`, `.github/`).\n\n## Optional subsystems stay off the core path\n\n`src/lab/` (Compatibility Lab) is opt-in. A user who configures one provider and\none model — no routing profile, no Lab — must execute no Lab code and start no\nLab timer.\n\nThree files carry every such user's request path and must not reach `src/lab/`,\ndirectly or transitively:\n\n- `src/router.ts`\n- `src/server/lifecycle.ts`\n- `src/server/responses/core.ts`\n\n`tests/lab/core-lab-boundary.test.ts` enforces this by walking the runtime import\ngraph and printing the offending chain on failure. It is not a style rule: the\noriginal violation hid in a six-hop chain\n(`assemble → quota → auth-api → native-main-admission → lifecycle → lab`) where\nno single file looked wrong, and it pulled ~69 Lab modules into every install.\n\nAn optional subsystem registers into a core-owned slot at activation instead of\nbeing imported. The existing seams are `src/server/passive-route-linker.ts`,\n`src/routing/compatibility/provider-slot.ts`, and\n`src/lib/optional-shutdown-hooks.ts`.\n\n`src/server/index.ts` is deliberately exempt: a composition root is supposed to\nknow which optional subsystems exist. Its obligation is the gate, not the import\n— activation must stay behind `labActivationRequired`, and it must stay\nsynchronous. Everything between `Bun.serve` and the return of `startServer` runs\nin one synchronous turn, which is what guarantees a policy route can never be\nevaluated before its evidence provider is registered. The synchronous\nsubagent-fallback chain has nowhere to await, so an `await` added before the\nactivation block would silently reroute subagents to a different model than the\noperator configured.\n\nThat one is enforced too, in the same file: a scan reads the window between the\n`Bun.serve` call and the `labActivationRequired` check and fails on any `await`\nthat would suspend `startServer` itself, plus on `startServer` being declared\n`async`. It has to ignore comments, string bodies, and nested functions to be\nusable, because the window legitimately contains three awaits inside the\n`server.stop` closure and two comments that mention the word. Until it existed,\nthis paragraph was the only thing holding the guarantee.\n\nDesign and audit history: `devlog/_fin/260814_lab_core_decoupling/`.\n\n## The `devlog` directory\n\nPlanning notes, triage matrices, and investigation artifacts live in `devlog/`,\ntracked like any other documentation. There is no submodule and no private\nmirror. It was a private submodule until the pointer churn outgrew its value:\n1723 commits touched the gitlink, and `dev`, `preview`, and `main` each carried a\ndifferent pointer, so every branch move and promotion dragged a diff.\n\n- `devlog/_plan/` — units still open, one directory per unit, decade-numbered\n  docs.\n- `devlog/_fin/` — closed units, moved here once a terminal outcome is recorded.\n  A `_fin` unit is a record of work already visible in public git history.\n- `devlog/_chase/` — external reference material for parity comparisons.\n  Reference *clones* are gitignored: they are third-party source carrying their\n  own licenses and have no business in this repository's history.\n\nNothing in the build, typecheck, or test path reads from `devlog/`, so a\ncontributor who ignores it entirely still passes every gate. `privacy:scan` does\nread it — that is deliberate, and it is what makes a public devlog safe rather\nthan merely visible.\n\nTwo mechanical guards in `tests/ci-workflows/repo-hygiene.test.ts` back this up: no `160000`\ngitlink may be tracked anywhere, and neither the vendored reference clones nor\nthe security triage excised before publication may reappear in the index. Both\nwere driven red once to prove they are not vacuous. The gitlink assertion exists\nbecause a gitlink in a tree CI does not initialize breaks `actions/checkout` for\nevery contributor, which happened twice.\n\n## Security working notes\n\n**Security work is done in scratch space, never in a tracked directory.** That\nincludes unreleased findings, severity assessments, draft advisories, exploit\nor bypass reasoning, reproduction steps for an unfixed defect, and\npre-disclosure patch plans.\n\nUse `.tmp/` in the working tree (already gitignored) or a `mktemp -d` path.\n`devlog/` is **not** an acceptable location — it is a public directory in a\npublic repository, so anything committed there is disclosed the moment it is\npushed, and the history is not practical to purge afterwards. A private\nrepository is not acceptable either: it gets cloned across machines and CI and\noutlives the embargo.\n\n**This binds maintainers exactly as it binds contributors and agents.** The rule\nhas been violated by maintainer-authored triage before: two units of open\nsecurity review accumulated under `devlog/_plan/` and had to be excised before\nthis directory could be published. Seniority is not an exemption, and \"it is\nonly in the private half\" is no longer a thing that exists.\n\nThe test to apply before writing a security note into `devlog/`: **is there\nalready a public diff that reveals this weakness?** If the fix has shipped, the\nwriteup discloses nothing new and belongs in `_fin/`. If it has not, the note is\npre-disclosure material and goes to scratch. That distinction is why closed\nhardening records stay in the tree while open triage does not.\n\nOnly the published outcome reaches a repository — the fix itself, its\nregression test, the release note, the advisory once it is public. Draft the\nadvisory in scratch space and delete the scratch directory once the advisory is\nlive.\n\nThis applies to `AGENTS.md`-following agents as much as to humans. If a task\nasks you to write up a security finding, put the write-up in scratch space and\nsay where it is; do not add it to `devlog/`, `structure/`, or `docs-site/`.\n\n## User-consent actions\n\nSome actions write to the **user's own accounts and identity** rather than to\nthis repository, and an agent must never perform or auto-answer them. One example\nis starring the repository on GitHub, which only comes up when\nan agent is *running* opencodex — not when it is working on this codebase.\n\nThe rule lives in [`AGENTS_INSTALL.md`](./AGENTS_INSTALL.md), which is the file\nan installing or operating agent reads. It was moved out of here because a\ndevelopment-facing file is the wrong place to trigger on it: this file is loaded\nfor every code change, and the consent boundary applies to none of them.\n\nWhat matters for development work: the enforcement is code, not prose —\n[`src/cli/agent-driven.ts`](./src/cli/agent-driven.ts),\n[`src/cli/star-prompt.ts`](./src/cli/star-prompt.ts), and\n[`src/server/management/sidebar-routes.ts`](./src/server/management/sidebar-routes.ts),\ncovered by `tests/server/startup-prompt.test.ts`, `tests/cli/agent-driven.test.ts`, and\n`tests/server/sidebar-routes.test.ts`. If you add another action that spends the user's\nidentity, credits, or reputation, gate it the same way rather than relying on a\nprompt an agent can answer, and document it in `AGENTS_INSTALL.md`.\n\n**Be clear about what that enforcement is and is not.** The management endpoint\nrequires a dashboard session, which stops the casual path — an agent that would\nhave POSTed there because the endpoint existed, and one holding only the admin\ntoken. It is not a technical barrier against a determined local agent: a process\nrunning as the user can mint its own session from the loopback dashboard\nbootstrap, and can skip the proxy entirely by running `gh` itself. Every local\ncredential is equally reachable by both the browser and the agent, so no check\ninside this process can tell them apart. The real boundary is the rule above, and\nit binds you regardless of which mechanism is within reach.\n\n## Commands\n\n```bash\nbun install\nbun run typecheck      # bun x tsc --noEmit (strict)\nbun run test:changed   # import-graph tests against the resolved `dev` merge base\nbun run test           # full tests/ suite (default before review)\nbun run lint:gui       # GUI eslint\nbun run privacy:scan   # credential/privacy scan used by CI\nbun run structure:check # structure/ doc-map, ownership, and invariant-binding gate\nbun run structure:index # regenerate structure/INDEX.md from structure/manifest.json\nbun run build:gui      # Vite GUI build\n```\n\n`skills/ocx/` is the operating reference for the CLI — what an agent reads to *drive* a running\nproxy, as opposed to [`AGENTS_INSTALL.md`](./AGENTS_INSTALL.md) (installing and operating consent)\nor this file (changing the codebase). Its surface map is generated:\n\n```bash\nbun run skill:surface        # regenerate after adding a capability\nbun run skill:surface:check  # what CI asserts\n```\n\n`tests/ci-workflows/skill-ocx.test.ts` fails if the committed map drifts from `src/cli/capabilities.ts`, and\nalso if the hand-written pages name a command the registry does not have. That second check is not\nhypothetical: it caught a documented `ocx request-history` that never existed.\n\nRun the test suite for a change; `bun run test` is the default before a\nnon-trivial PR is marked review-ready or approved. During implementation, use\nfocused files or `bun run test:changed` for faster feedback.\n\nIf a full local run is disproportionately expensive for the task or available\nresources, including contention across concurrent worktrees, run at least the\nfocused regression tests that exercise the changed behavior. This is a scope\nexception, not permission to skip testing or ignore a failing test. Record why\nthe full run was impractical, the exact commands and results, and the coverage\nleft to CI in the PR's Verification section. Never describe an unrun suite as\npassing. Run `bun run typecheck` before review readiness as well.\n\n`bun run test:changed` follows Bun's parsed module graph, so it cannot discover\ndependencies expressed through subprocesses, source files read as data, or\ngolden/derived files. Run those relevant regression files explicitly. If a\nfocused set cannot reliably cover the change, keep the PR in draft until the\nbroader validation is available.\n\nAfter pushing, inspect the required CI for the current PR head. Missing,\nawaiting-approval, skipped, cancelled, or older-head results are not passing\nevidence. Required checks must actually complete successfully before merge.\nThe repository does not install an automatic pre-push validation hook;\n`bun run prepush` remains available as an explicit comprehensive check.\n\nDo not rerun passing checks on unchanged code merely for additional confidence.\n\n## What a green pull request does not tell you\n\nExact-head CI cannot see a defect that exists only in the union of two changes. Each\nbranch is correct at its own head, the merge is not, and the failure lands on whoever\npushes next. One round produced ten of these, including an hour of red `dev`, so the\nclasses below are worth checking before you push rather than after.\n\n### The file-size ratchet has almost no headroom\n\n`tests/fixtures/file-size-baseline.json` records a line cap per file and\n`updateBaseline` uses `Math.min`, so a cap only ever moves **downward**. Raising one is\nnot possible by design, and a cap is not a suggestion you can negotiate with.\n\nAt the time of writing, 39 of the 51 tracked files sit at **exactly** their cap and three\nmore are within five lines. Among them are `src/server/index.ts`, `src/config.ts`,\n`src/server/responses/core.ts`, `gui/src/pages/Models.tsx`, and the large test files\n`tests/codex-integration/codex-catalog.test.ts` and\n`tests/responses/openai-responses-passthrough.test.ts`. Adding one line to any of them\nfails `file-size ratchet: repository` for your branch and for every branch cut from\n`dev` afterwards.\n\nTwo branches can each stay under a cap alone and sum over it together; that is what\nhappened in #4908, #5011 and #5018. The remedy is always a move, never a number: put the\nnew case in a sibling file, byte for byte, and register it in **both**\n`scripts/test-layout/layout.json` (or its domain shard) and a `tests/fixtures/test-layout-expected*.json` shard.\n`d3ca5522db` is the original precedent.\n\nA moved test is not automatically the same test. One case moved out of\n`codex-v2-gate.test.ts` failed in isolation and then failed again in place once unrelated\nblocks moved around it, because its final assertion was reading catalog state earlier\ncases had warmed rather than the contract. If a moved case changes colour, suspect the\ncase before the move.\n\n### Anything exhaustive over a union\n\nA locale catalog, a `satisfies Record<Union, ...>`, a hand-maintained roster, a count in\ngenerated documentation. Adding a member to the union in one branch while another branch\nadds a consumer keyed by it produces a merge that typechecks in neither direction.\n\n`typecheck` precedes every job, so one missing member is not one red suite. It took down\nfourteen checks on `dev` — all four test shards, `docker smoke`, `storage policy`,\n`api usage` and all three `npm-global` smokes — when a closed translation namespace still\nlisted nine locales after a tenth had landed.\n\nCounts drift the same way and more quietly, because both sides write a plausible number.\nTwo branches each added one CLI capability and each wrote `47`; the merged truth was 48.\nTwo each added one provider preset and each wrote `94`; the registry had 95. Prefer\nderiving a count or a member list from the thing it describes over restating it.\n\n## Minimal containers and agent sandboxes\n\nFresh dev containers and agent sandboxes (Cursor Cloud, devcontainers, CI\nimages) often ship Node but not Bun. Install it first:\n\n```bash\ncurl -fsSL https://bun.sh/install | bash   # installs ~/.bun/bin/bun\nexport PATH=\"$HOME/.bun/bin:$PATH\"\nbun install && (cd gui && bun install)\n```\n\nRun the proxy with `bun run src/cli/index.ts start --port <port>`. `/healthz`\nreports status, `/` serves the dashboard, and the management API requires the\nadmin token the server writes to `$OPENCODEX_HOME/admin-api-token` at startup.\n\n`bun run test` has five known environment-only failures in such containers.\nThey are not regressions; do not re-investigate them:\n\n- `service diagnostics > status summary exposes the service log path`,\n  `CLI subcommand help > status prints diagnostics without starting the proxy`,\n  and `CLI subcommand help > invalid service and codex-shim usage include\n  remove alias` require a running systemd init; in a container PID 1 is\n  typically `tini` or another minimal init, so service commands report\n  \"systemd not found\".\n- `package tree integrity > an in-place rewrite of the same byte length is\n  still a replacement` and `Codex Log Guard inspection > repeat inspection is\n  memoized and invalidated by a write` rely on filesystem mtime granularity\n  that some container filesystems do not provide.\n\nEverything else passes (15480 pass / 16 skip / 5 fail as of 2.35.0).\n\n## Issues and pull requests (agents)\n\nAgent-created issues and PRs must use the repository templates. The gates\nbelow enforce them, so a freeform or mismatched submission is rejected rather\nthan nudged.\n\n- **Creating an issue:** open it through the template chooser and use the\n  matching form in `.github/ISSUE_TEMPLATE/` — `bug_report.yml` (Bug report),\n  `feature_request.yml` (Feature proposal), `documentation.yml`\n  (Documentation), or `provider_compatibility.yml` (Provider or API\n  compatibility). Keep the form's section headings exactly as generated;\n  `enforce-issue-quality` validates the headings and closes untemplated or\n  mislabeled issues (`.github/ISSUE_TEMPLATE/config.yml` disables blank\n  issues, so there is no freeform fallback).\n- **Opening a pull request:** fill every section of\n  `.github/PULL_REQUEST_TEMPLATE.md` (Summary, Verification, Checklist).\n  `enforce-target` rejects empty, thin, or malformed descriptions. If the PR\n  changes files under `gui/`, include a screenshot of the UI change in the\n  description; the check re-runs on description edits until the screenshot is\n  present. Drag the image into the description editor rather than committing it:\n  an image on your branch rides the squash merge into `dev`. Maintainers\n  uploading from the command line use the `pr-assets` branch and link by commit\n  SHA. Never commit screenshot evidence to the PR branch — the squash merge carries\n  it into `dev`, which is how `docs/pr-assets/` and its siblings grew until\n  they were deleted; `tests/ci-workflows/repo-hygiene.test.ts` now rejects\n  those folders. When the PR resolves an issue, add\n  `Closes #<number>` to link it. GitHub auto-closes the linked issue only\n  when the PR merges into the default branch (`main`); PRs here target\n  `dev`, so close the issue manually once the change is on `dev`.\n- **Landing another author's work:** reimplementing, superseding, carrying, or\n  rebasing someone else's pull request requires a `Co-authored-by` trailer\n  naming that author, in the description or in a branch commit so it survives\n  the squash. Saying it in prose is not equivalent — the trailer is what GitHub\n  reads for the contributor graph, and a sentence in a commit body is read by\n  nothing. This repository did it both ways for months: `53c09a247` says \"Clean\n  reimplementation of #3193\" and names the author in a trailer, `5734a1caf` says\n  \"Reimplements #2797 by @rrmlima\" and names nobody, so that contribution is\n  invisible on its author's profile. The 27 landings already in that state are\n  recorded in [`CREDITS.md`](./CREDITS.md); `missing_coauthor_credit` in\n  `.github/scripts/pr-carry-attribution.cjs` is why the list should not grow.\n\n## CI policy (maintainer decision — do not change)\n\n- Pull requests run **only the fast Ubuntu subset**.\n- Full cross-platform (macOS/Windows) CI runs **only** via manual\n  `workflow_dispatch` and on push to `main`/`preview` (release lines).\n- **Never** add a `dev` push trigger, or `pull_request` runs for macOS/Windows\n  jobs, to any workflow. PRs that change this policy will be closed.\n\n## Branch policy\n\n- `dev` — the single integration branch and the target for every pull request.\n- `main` — release branch. It only moves by maintainer-controlled promotion\n  from `dev` (releases, docs deploys). Do not open feature PRs against `main`.\n- `preview` — prerelease train (`x.y.z-preview.*` versions).\n\nBun-native TypeScript on `dev` is the only runtime line. If native code\nreturns, the expectation is an incremental module (for example Rust via N-API)\nlanding on `dev`, not a second full-runtime branch.\n\nStacked child pull requests that target another **open** PR's head branch are\nan intentional review workflow, not an alternate integration line. The\n**`enforce-target`** check skips the wrong-base gate for those children; after\nthe parent lands or closes, retarget the child to `dev`.\n\nRebase pull requests are welcome. Bringing a stale branch onto the current head\nis ordinary maintenance — open it as a normal pull request and name the source\ncommits in the description.\n\nThe **`enforce-target`** CI check rejects pull requests whose head\nancestry sits on the **`main`** tip while far behind **`dev`**, and rejects\nempty, thin, or malformed descriptions. If changed paths include files under\n`gui/`, include a screenshot of the UI change in the description; the check\nre-runs on description edits until the screenshot is present. Drag the image\ninto the description editor rather than committing it, or, when uploading from\nthe command line, use the `pr-assets` branch and link by commit SHA.\nContributor PRs (authors without repository push permission) open in draft and\nstay there until a four-box review-readiness checklist in the description is\ncomplete: required local validation passed with its scope documented, branch\non the latest `dev` commit, all correct Codex and CodeRabbit findings fixed,\nand the ready-for-review confirmation. When all\nfour boxes are ticked the gate marks the PR ready and notifies the maintainers\nlisted in `MAINTAINERS.md` (excluding the author). Completion is bound to the\nexact commit the PR head pointed at: if new commits are pushed afterwards, the\ngate moves the PR back to draft, resets the checklist and the notification,\nand asks the author to test and tick the boxes again against the latest code.\nBefore a completion is accepted, the gate verifies the checklist claims it\ncan check itself: the branch must be on the latest `dev` commit or at most\n10 commits behind it, and Codex/CodeRabbit findings must be resolved. The\nlocal-validation box is an author attestation only — fork contributors cannot start\nrepository CI; a maintainer has to — so the gate never disproves it; a new\npush still resets every box. A disproved claim unticks the matching box and\nkeeps the PR a draft.\nAuthors with repository push permission skip the ancestry heuristic only. As with approval requirements in\n[`MAINTAINERS.md`](./MAINTAINERS.md), the ancestry heuristic is a CI check\nrather than a branch rule. The branches themselves are protected: `dev`,\n`main`, and `preview` each require a pull request and block force-pushes and deletion.\nFor `dev` only, a current maintainer with GitHub `maintain` or `admin` access may\nexplicitly integrate through a PR without another maintainer approval, including\ntheir own PR, under the policy in `MAINTAINERS.md`. Record the decision and exact-head\nCI evidence; keep outstanding maintainer objections and security review separate.\nThe bypass is PR-only, so a direct push to `dev` remains rejected regardless of\nlocal hook settings. Contributor review and `main`/`preview` rules remain unchanged.\n\n[`MAINTAINERS.md`](./MAINTAINERS.md) is authoritative for review and merge\npolicy (approvals, CI requirements, security review, promotion). This file\nsummarizes; it never overrides it.\n\n## Review guidelines\n\nThese rules apply to all code reviews on this repository, including automated\nreviewers (Codex, CodeRabbit).\n\n- **Language:** always review in English, regardless of the PR or issue\n  language. Be detailed and specific: name the file and line, describe the\n  concrete failure mode, and suggest a fix. Avoid vague or purely stylistic\n  commentary.\n- **Branch targeting:** flag any pull request that does not target `dev`\n  (releases and maintainer promotions are the only exceptions).\n- **Security boundary (highest priority):** changes touching authentication,\n  credential/token handling, OAuth flows, GitHub Actions workflows, release\n  automation (`scripts/release.ts`, `.github/workflows/release.yml`), or\n  dependency installation require explicit security review per\n  `MAINTAINERS.md`. Treat token logging/serialization, secret exposure,\n  workflow permission escalation, and mutable third-party action refs as\n  release blockers.\n- **Runtime constraints:** the proxy is Bun-native. Flag Node-only APIs,\n  assumptions about a compile step, or code paths that break `bun run\n  typecheck` / `bun run test`.\n- **Tests:** behavior changes in `src/` need a focused regression test near\n  the existing tests for that subsystem. During implementation, run the relevant\n  focused files and use `bun run test:changed` for import-connected coverage as\n  described above. Full-suite validation is the default before review readiness;\n  the documented resource exception still requires focused regression tests.\n- **Docs sync:** user-facing behavior changes should update `docs-site/` (and\n  keep translated locales from contradicting the English source).\n- **Privacy:** `bun run privacy:scan` must stay green; never introduce logging\n  of request bodies, API keys, or account identifiers.\n\n<!-- File: .claude-plugin/marketplace.json (Tokens: ~300 | Category: marketplace) -->\n# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.claude-plugin/marketplace.json)\n\n<!-- File: .claude-plugin/plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.claude-plugin/plugin.json)\n\n<!-- File: .cursor/mcp.json (Tokens: ~300 | Category: mcp-config) -->\n# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/lidge-jun/opencodex/HEAD/.cursor/mcp.json)\n</yakaai_skills>"}