Skip to content

Latest commit

 

History

History
387 lines (301 loc) · 15.6 KB

File metadata and controls

387 lines (301 loc) · 15.6 KB

Contributing to codex-lb

Thanks for thinking about contributing to codex-lb! This document covers everything you need to get from "clone" to "merged PR" without re-discovering the conventions yourself.

If anything below is unclear or out of date, please open a small docs: PR — that's the easiest first contribution.


Table of contents

  1. Code of conduct
  2. Ways to contribute
  3. Development setup
  4. Project layout
  5. Workflow: OpenSpec-first
  6. Coding conventions
  7. Commit & PR conventions
  8. Merge gates and collaborator rules
  9. Tests
  10. Release process
  11. Security issues

Code of conduct

Participation in this project is governed by the Contributor Covenant Code of Conduct. By participating, you agree to uphold it.

Ways to contribute

You don't need to write code to help:

  • Triage: reproduce open issues, ask for missing details, add labels.
  • Docs: improve the README, fix typos, document a deployment recipe.
  • Discussions: answer "how do I…?" questions in GitHub Discussions.
  • Bug reports: file a structured report using the Bug Report issue form.
  • Feature ideas: propose them in Discussions first if open-ended; use the Feature Request form when the idea is concrete.
  • Code: fix a bug, implement a feature, refactor a hot spot.

Look for issues labelled good first issue or help wanted if you're looking for a starter task.

Development setup

codex-lb is a Python project managed with uv.

# 1. Fork & clone
git clone https://github.com/<your-user>/codex-lb.git
cd codex-lb

# 2. Install Python + deps via uv
uv sync --all-extras --dev

# 3. Activate the venv (optional — `uv run` works without activating)
source .venv/bin/activate

# 4. Install pre-commit hooks
uv run pre-commit install

# 5. Run the proxy locally
uv run codex-lb --help

Frontend (dashboard) lives under frontend/. Use the project's standard package manager (see frontend/package.json).

Project layout

.
├── app/                  # Python application (proxy, accounts, dashboard backend)
├── frontend/             # Dashboard SPA
├── deploy/helm/codex-lb/ # Helm chart
├── tests/
│   ├── unit/             # Fast, isolated tests
│   └── integration/      # End-to-end / network-touching tests
├── openspec/             # SSOT — specs, change proposals, archived changes
│   ├── specs/<capability>/
│   └── changes/<change>/
└── .github/              # Issue forms, PR template, workflows

Workflow: OpenSpec-first

codex-lb uses OpenSpec as the source of truth for change-driven development. You don't have to author OpenSpec entries for trivial fixes, but for anything that changes observable behavior, requirements, contracts, or schema, an OpenSpec change is expected.

The typical flow:

  1. Find the relevant spec in openspec/specs/<capability>/spec.md.
  2. If your change alters behavior: create openspec/changes/<kebab-case-change>/ with a proposal + tasks.
  3. Implement the tasks; keep spec.md in sync.
  4. Validate: openspec validate --specs.
  5. Verify and archive when done.

If you're unsure whether your PR needs an OpenSpec change, open it as draft and ask — a maintainer will tell you.

Do not hand-edit CHANGELOG.md. Release notes are generated by release-please from your commit titles.

Coding conventions

  • Python: 3.13+. Code is formatted with ruff format, linted with ruff check, and type-checked with ty (Astral's type checker).
  • Imports / style: follow what you see in nearby files; the linter is authoritative — if ruff and your style guide disagree, ruff wins.
  • Type hints: required on new public functions and new modules. Existing code is being typed incrementally.
  • Logging: prefer the project's structured logger over print.
  • Secrets: never commit API keys, tokens, account credentials, or .env files. The CI redaction is best-effort, not a safety net.

Run the full lint/test gate locally before pushing:

uv run pre-commit run local-ci --hook-stage manual --all-files

The local-ci hook runs make ci, which is the local version of the GitHub Actions CI gate. You can also run a single CI job while iterating, for example:

make lint
make test-unit
make package

Commit & PR conventions

codex-lb uses Conventional Commits because release-please derives the next version + changelog from them.

Format:

<type>(<scope>)?: <subject>

[optional body]

[optional footer(s)]

Common types:

Type When to use it
fix Bug fix → patch version bump
feat New user-facing feature → minor version bump
refactor Internal change, no behavior change
perf Performance improvement
docs Documentation only
test Test-only change
build Build system / packaging
ci CI configuration
chore Tooling / maintenance with no user-visible effect

Common scopes used in this repo: proxy, accounts, auth, oauth, quota, chat, proxy-responses, dashboard-auth, ui, db, types, openspec, main (release).

Breaking changes: append ! after the type/scope and/or add a BREAKING CHANGE: footer.

Examples (from the actual repo history):

fix(proxy): handle model fetch timeouts
fix(accounts): recover quota status from usage refresh
feat(proxy): add OpenAI-compatible /v1/images API (gpt-image-2 via image_generation tool)
fix(dashboard-auth): reject passwords longer than bcrypt's 72-byte limit

PR titles must follow the same format — that's the title release-please reads.

Pull request flow

  1. Create a branch from main: git checkout -b <type>/<short-name>.
  2. Make atomic commits with Conventional Commit titles.
  3. Run the lint/test gate locally (see above).
  4. Open a PR using the template. Link the relevant issue.
  5. CodeRabbit (and a human maintainer) will review. Address feedback by pushing follow-up commits — no force-pushing during active review.
  6. Once approved and CI is green, a maintainer squash-merges with a clean Conventional Commits title.

Merge gates and collaborator rules

These rules apply to every PR, regardless of author (external contributor, project owner, or collaborator). They formalize the review bar that has produced the current proxy / continuity-recovery / OpenSpec quality so far.

Merge gates

Before a PR is squash-merged into main:

  1. CI must be all-green on the merge-target head. "UNSTABLE, looks fine" is not a green CI; rerun, fix, or wait. The Helm / migration / PostgreSQL test jobs are part of the gate, not optional. The CI Required check is the branch-protection check to require: it depends on every CI job and also runs for merge queue synthetic merge groups, so a stale PR head cannot bypass a broken merge result.
  2. Actionable CodeRabbit findings must be fixed or explicitly addressed or dismissed in-thread on the merge-target head. Review the current-head CodeRabbit findings before merging; no finding may be silently skipped. Local codex review --base origin/main runs remain an encouraged extra tool, but they are not a merge gate and do not substitute for CodeRabbit.
    • P1 findings: fix in the PR, or justify in-thread with a short write-up of why the finding doesn't apply. No silent skipping.
    • P2 findings: fix in the PR, or open a follow-up issue and link it in the PR thread before merging.
  3. mergeable must be CLEAN in the GitHub API — no merge conflicts, no requested-changes review still outstanding, no missing required status check.
  4. OpenSpec change folder for behavior changes (see Workflow: OpenSpec-first). Pure refactors, docs-only edits, dev-tool changes, and test stabilization PRs are exempt; everything else needs an openspec/changes/<slug>/ entry.
  5. Fixes #N / Closes #N in the PR body for anything that resolves an issue, so the issue close stays automatic and the merge stays traceable. Use Refs #N / Related to #N for partial cover.
  6. Simplicity gates must pass (see Simplicity gates): the five simplicity rules (PRINCIPLES.md P1-P5). Budget exceptions need the maintainer-applied simplicity-budget-approved label.

Simplicity gates

These implement PRINCIPLES.md; the normative spec is openspec/specs/contribution-simplicity/spec.md (created when the codify-simplicity-principles change is archived). Reviewers apply them to every PR (budget checks are enforced by CI as of the ci-simplicity-budgets change; reviewer-enforced before that):

  1. New features default to off or zero-config. No new required setup step (env var, migration action, external account, manual file edit) on the base install path without maintainer approval via the simplicity-budget-approved label.
  2. Every new CODEX_LB_* setting justifies not being a default. The PR body answers "why can't this be a hardcoded default?" for each new setting; internals-only knobs stay out of .env.example.
  3. README, .env.example, and dashboard nav are budgeted. The caps live in .github/simplicity-budgets.toml (introduced by the ci-simplicity-budgets change; until that manifest exists on main, reviewers judge growth of these surfaces directionally rather than against numeric caps). Exceeding a cap requires the maintainer-applied simplicity-budget-approved label before merge.
  4. Feature docs go to docs/ + OpenSpec, never new README sections. Each spec-governed docs page links back to its openspec/specs/<capability>/ entry.
  5. Dashboard-visible PRs include before/after screenshots (or a short recording) in the PR body.

Collaborator rules

Collaborators (write-access contributors) follow two additional rules on top of the merge gates above:

  1. No self-merge by default. A collaborator's own PR is merged by another maintainer (or, until the project has more collaborators, by the project owner). Review independence matters more than turnaround.
  2. Large PRs get split. Roughly:
    • If a PR is a stack tip pulling in unrelated commits from sibling branches, split it so each merged PR is a single scoped change.
    • If a single PR is over ~800 net lines and spans multiple concerns, split it into reviewable pieces. A single 1500-line change scoped to one capability is fine; a 400-line change that touches the proxy hot path and the dashboard and the OAuth flow is not.

Bus factor escape hatch

To keep the project unblocked if the owner is unavailable, the following self-merge escape hatch applies:

  • If a collaborator's PR has been waiting on a maintainer merge for more than 14 days with all merge gates met (CI green, CodeRabbit findings addressed, mergeable=CLEAN, no outstanding requested-changes review, no objection from any other active collaborator in the thread), the PR author may self-merge.
  • Self-merge under this clause must include a comment on the PR explicitly invoking the clause and linking to the date the merge gates first went green. Audit trail must stay clean.
  • The clause is a safety valve, not a default path. If you're tempted to invoke it on a PR you opened less than two weeks ago, the merge gates probably aren't actually all green yet.

What this is not

These rules are intentionally lightweight. They don't require:

  • A second human reviewer in addition to CodeRabbit for every PR. CodeRabbit review + the PR author + a maintainer merge is the baseline.
  • Squash-merge commit message rewriting beyond the Conventional Commits title. The PR description ends up in the body; that's enough.
  • A formal escalation process for disagreements. If a P1 finding is disputed, work it out in-thread; if it can't be resolved, leave the PR open and ping the owner.

Tests

  • Unit tests live under tests/unit/ and should be fast and hermetic.
  • Integration tests live under tests/integration/ and may spin up the app, hit local sockets, or use fixtures that mimic upstream.
  • New behavior needs at least one unit test. New endpoints need an integration test.
  • Run a focused subset with:
uv run pytest tests/unit/test_proxy_api_responses_contract.py -q

Release process

Releases are automated via release-please:

  1. Commits land on main with Conventional Commits titles.
  2. release-please opens / updates a "chore(main): release X.Y.Z" PR containing the proposed version bump and generated changelog.
  3. When a maintainer merges that PR, a GitHub Release is published and the PyPI / Docker / Helm artifacts are built and uploaded.

Contributors never need to edit CHANGELOG.md, version strings, or tag manually.

Release channels: beta first

Stable releases are promoted from the beta channel, not cut directly:

  1. A vX.Y.Z-beta.N release ships first (the beta release PR).
  2. The beta soaks on at least one production-scale deployment for at least 48 hours with no new regressions attributable to the release.
  3. Only then is the stable vX.Y.Z release PR merged.

Rationale: migrations and proxy-path changes routinely behave differently at production data volumes than in CI. The beta soak is where migration duration, memory pressure, and upstream-protocol regressions surface without burning a stable version number.

Exceptions — a maintainer may promote directly to stable, noting the reason in the release PR, when the entire unsoaked delta consists of (any combination of):

  • documentation, CI, or release-tooling changes, or
  • a security or outage hotfix where waiting out the soak is the greater risk.

A train that also carries unrelated unsoaked changes must either soak as a beta or ship the hotfix separately.

Before merging a release PR whose train includes migrations, inspect the Alembic revisions directly (git diff vPREV..HEAD -- app/db/alembic/versions) for data backfills and estimate their duration against a production-sized dataset — they run at startup and block serving until they finish. Changelog titles do not reveal backfills.

The normative requirements live in openspec/specs/release-management/ (delta: openspec/changes/require-beta-soak-before-stable/).

Security issues

Please do not open public issues for security vulnerabilities. Report them privately via GitHub Security Advisories. See SECURITY.md for full details.


Happy hacking. If you get stuck, open a draft PR or start a Discussion — maintainers would rather see a half-finished idea than no idea at all.