diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 8d9ae45..b172ad0 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -10,9 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 + persist-credentials: false - name: Validate git-cliff changelog generation uses: orhun/git-cliff-action@v4 diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml index 9d0fd4b..32eaf96 100644 --- a/.github/workflows/commitlint.yml +++ b/.github/workflows/commitlint.yml @@ -9,9 +9,10 @@ jobs: commitlint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 + persist-credentials: false - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d5944d1..ea5e627 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,10 +18,10 @@ jobs: id-token: write steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 0d08c20..7d263b8 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -15,10 +15,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ["3.1.3", "3.4", "4.0.3"] + ruby-version: ["3.3", "3.4", "4.0.3"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 + with: + persist-credentials: false - name: Set up Ruby uses: ruby/setup-ruby@v1 @@ -33,9 +35,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 + persist-credentials: false - name: Validate git-cliff changelog generation uses: orhun/git-cliff-action@v4 diff --git a/.standard.yml b/.standard.yml index 72b2693..49bff5f 100644 --- a/.standard.yml +++ b/.standard.yml @@ -1 +1 @@ -ruby_version: 3.1 +ruby_version: 3.3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 45cc209..e77ea9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 experiments. - Add isolated execution defaults, timeout handling, and process-group cleanup. - Add live smoke coverage for plain-text and structured-output requests. +- Add CI, Conventional Commit checks, changelog validation, and tag-driven + RubyGems trusted publishing. +- Document installation, configuration, limitations, troubleshooting, + contribution, security, and release procedures. ### Fixed - Disable unnecessary Codex shell snapshots to prevent Bash process explosions during completion-only runs. +- Align the documented Ruby requirement, CI matrix, syntax target, and locked + development dependencies on Ruby 3.3 or newer. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..bcc196c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,30 @@ +# Code of Conduct + +## Our standard + +We are committed to a welcoming, respectful, and harassment-free community. +Examples of positive behavior include empathy, constructive feedback, respect +for differing viewpoints, and taking responsibility for mistakes. + +Unacceptable behavior includes harassment, discriminatory language, personal +attacks, deliberate intimidation, publishing another person's private +information, or conduct that would reasonably be considered inappropriate in a +professional setting. + +## Enforcement + +Project maintainers may remove, edit, or reject comments, commits, issues, and +other contributions that violate this standard. They may temporarily or +permanently restrict participation when necessary to protect the community. + +Report conduct concerns privately to `devel@ethos-link.com`. Maintainers will +respect the reporter's privacy and evaluate reports fairly. Retaliation against +someone who reports a concern is prohibited. + +## Scope + +This policy applies in project spaces and when an individual is publicly +representing the project or its community. + +This policy is adapted from the +[Contributor Covenant](https://www.contributor-covenant.org/), version 2.1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1ea6ff6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing + +Thank you for helping improve `ruby_llm-codex`. + +## Before opening a change + +- Use an issue for user-visible behavior changes or proposals that need design + agreement. +- Use [GitHub's private vulnerability reporting](SECURITY.md) for security + issues; do not disclose them publicly. +- Keep the adapter's scope narrow: non-streaming RubyLLM chat through the local + Codex CLI, with isolation and reproducibility as defaults. + +## Development setup + +```bash +git clone git@github.com:ethos-link/ruby_llm-codex.git +cd ruby_llm-codex +bundle install +bundle exec lefthook install +bundle exec rake +``` + +The authenticated smoke test is separate because it invokes the real Codex CLI +and consumes subscription allowance: + +```bash +bundle exec rake codex:smoke +``` + +Run it inside an appropriate process and memory boundary when changing process +lifecycle, timeout, or shell configuration behavior. + +## Pull requests + +1. Create a branch; hooks reject direct commits to `main` and `master`. +2. Add focused tests for behavior changes. +3. Run `bundle exec rake` and any relevant live smoke coverage. +4. Use Conventional Commits, including a meaningful body for non-trivial work. +5. Update `README.md` and `CHANGELOG.md` when user-facing behavior changes. +6. Open a pull request against `main` and explain the behavior, risks, and + verification performed. + +Do not include generated gems from `pkg/`, credentials, Codex session data, or +unrelated formatting changes. + +## Releases + +Only maintainers publish releases. See [docs/releasing.md](docs/releasing.md) for +the controlled release procedure. diff --git a/README.md b/README.md index e51c972..5a7ce0e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # RubyLLM Codex provider +[![Ruby](https://github.com/ethos-link/ruby_llm-codex/actions/workflows/ruby.yml/badge.svg)](https://github.com/ethos-link/ruby_llm-codex/actions/workflows/ruby.yml) +[![Gem Version](https://badge.fury.io/rb/ruby_llm-codex.svg)](https://rubygems.org/gems/ruby_llm-codex) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + This small adapter lets RubyLLM invoke the official local Codex CLI as a provider. Codex reuses the ChatGPT login stored by `codex login`, so these runs consume your Codex subscription allowance rather than OpenAI API credits. @@ -15,7 +19,7 @@ mapping but not an identical wire-level experiment. ## Requirements -- Ruby 3.1.3 or newer +- Ruby 3.3 or newer - RubyLLM 1.16.0 or newer, below 2.0 - Codex CLI 0.144.5 or newer, available as `codex` - Linux or macOS (timeout cleanup relies on POSIX process groups) @@ -31,26 +35,23 @@ codex login Check the model list in your installed Codex version before a large run. Model availability depends on the account and currently installed CLI. -Run the automated unit tests and the two-request live smoke task with: +## Installation -```bash -bundle exec rake test -bundle exec rake codex:smoke -``` +Add the gem to your application's `Gemfile`: -The smoke task verifies the installed CLI version, then performs one plain-text -completion and one structured-output completion through the real RubyLLM -provider. It consumes Codex subscription allowance and requires a completed -login. Set `CODEX_MODEL`, `CODEX_BIN`, or `CODEX_TIMEOUT` to override its model, -executable, or 300-second request timeout. +```ruby +gem "ruby_llm-codex", "~> 0.1" +``` -For local development, add this project to your application: +Then run `bundle install`. You can also install it directly: -```ruby -gem "ruby_llm-codex", path: "/path/to/ruby_llm-codex" +```bash +gem install ruby_llm-codex ``` -Then use it like any explicit RubyLLM provider: +## Usage + +Require the gem and use Codex as an explicit RubyLLM provider: ```ruby require "ruby_llm-codex" @@ -91,6 +92,10 @@ By default every request: This prevents local `AGENTS.md`, plugins, MCP servers, repository contents, and saved session history from quietly affecting a comparison. +## Configuration + +### Per request + To deliberately let Codex inspect a repository or use selected configuration: ```ruby @@ -106,6 +111,8 @@ chat.with_params( ) ``` +### Global + Global configuration is also available: ```ruby @@ -145,6 +152,18 @@ If you later need streaming, persistent threads, or high throughput, replace the process-per-request implementation with a long-running `codex app-server` client while keeping the same RubyLLM provider surface. +## Troubleshooting + +- **`codex` is not found:** install the Codex CLI, run `codex login`, or set + `RubyLLM.config.codex_cli_path` to its absolute path. +- **A request times out:** increase `codex.timeout` or `CODEX_TIMEOUT` for the + smoke task. Timed-out process groups are terminated automatically. +- **Codex consumes excessive memory:** use the current gem version. The provider + explicitly disables Codex shell snapshots, which are unnecessary for its + completion-only execution and previously caused runaway Bash processes. +- **Local instructions affect output:** keep `ignore_user_config: true` and do + not set a working directory when you need isolated comparisons. + ## Development ```bash @@ -155,8 +174,15 @@ bundle install bundle exec rake ``` +To test an unreleased checkout from another application, use a local path in +that application's `Gemfile`: + +```ruby +gem "ruby_llm-codex", path: "/path/to/ruby_llm-codex" +``` + The default Rake task runs the unit suite and Standard Ruby. CI runs the same -gate on Ruby 3.1.3, 3.4, and 4.0.3, then validates commit messages and +gate on Ruby 3.3, 3.4, and 4.0.3, then validates commit messages and git-cliff changelog generation. The authenticated smoke task is intentionally separate because it consumes @@ -184,10 +210,10 @@ bundle exec lefthook install ## Release -Releases are tag-driven. Local release commands prepare the changelog, version -commit, and tag; GitHub Actions runs the full test matrix, publishes through -RubyGems trusted publishing, and creates the GitHub release from -`CHANGELOG.md`. +Releases are tag-driven. GitHub Actions runs the full test matrix, publishes +through RubyGems trusted publishing, and creates the GitHub release from +`CHANGELOG.md`. See [docs/releasing.md](docs/releasing.md) for repository setup, +the first-release procedure, verification, and recovery guidance. Install [git-cliff](https://git-cliff.org/) locally, check out a clean `main`, and run one of: @@ -199,22 +225,21 @@ bundle exec rake 'release:prepare[major]' bundle exec rake 'release:prepare[0.2.0]' ``` -The task: +For releases after `0.1.0`, the task: 1. Prepends the next changelog section from Conventional Commits. 2. Updates `lib/ruby_llm/codex/version.rb`. 3. Commits the release files and creates `vX.Y.Z`. 4. Pushes `main` and the tag to `origin`. -Before the first automated release, configure the gem's RubyGems trusted -publisher with owner `ethos-link`, repository `ruby_llm-codex`, workflow -`release.yml`, and environment `release`. The environment name must match the -protected GitHub environment used by the release job. +The initial `0.1.0` release only needs its existing version commit tagged after +the GitHub environment and pending RubyGems trusted publisher are configured. ## Contributing -Create a non-main branch, keep commits scoped and conventional, run -`bundle exec rake`, and open a pull request against `main`. +See [CONTRIBUTING.md](CONTRIBUTING.md). By participating, you agree to follow +the [Code of Conduct](CODE_OF_CONDUCT.md). Security reports should follow +[SECURITY.md](SECURITY.md), not a public issue. ## License diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..b9ac09d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,34 @@ +# Security policy + +## Supported versions + +Security fixes are provided for the latest released version. Because the gem is +pre-1.0, upgrades may include small compatibility changes documented in the +changelog. + +## Reporting a vulnerability + +Do not open a public issue for a suspected vulnerability. Use GitHub's private +vulnerability reporting for this repository. If that is unavailable, email +`devel@ethos-link.com` with: + +- the affected version and platform; +- a minimal reproduction or proof of concept; +- the expected security impact; +- any suggested mitigation; and +- whether the issue is already public. + +You should receive an acknowledgement within five business days. We will +coordinate validation, remediation, and disclosure with the reporter. + +## Security boundaries + +This gem starts the locally installed Codex CLI and reuses its existing ChatGPT +authentication. It does not collect, proxy, or store credentials. By default it +runs Codex in a fresh temporary directory, disables interactive approvals and +shell snapshots, uses a read-only sandbox, and does not inherit the parent +process environment for model-generated shell commands. + +Applications that override the working directory, sandbox, user configuration, +or environment inheritance assume responsibility for the additional access +granted to Codex. diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 0000000..4846c19 --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,79 @@ +# Releasing ruby_llm-codex + +Releases are immutable, tag-driven, and published by GitHub Actions through +RubyGems trusted publishing. Maintainers do not need a RubyGems API key. + +## One-time repository setup + +1. Push `main` to `git@github.com:ethos-link/ruby_llm-codex.git`. +2. In GitHub, create an environment named `release`. Add required reviewers if + the organization requires a manual publication gate. +3. Protect `main`, require pull requests, and require the Ruby, Commitlint, and + Changelog checks before merging. +4. In the RubyGems account that will own the gem, create a + [pending trusted publisher](https://rubygems.org/profile/oidc/pending_trusted_publishers) + with these exact values: + + | Field | Value | + | --- | --- | + | Gem name | `ruby_llm-codex` | + | GitHub owner | `ethos-link` | + | Repository | `ruby_llm-codex` | + | Workflow | `release.yml` | + | Environment | `release` | + +The workflow grants `id-token: write` only to the release job. RubyGems exchanges +that GitHub OIDC identity for short-lived credentials during publication. + +## Publish 0.1.0 + +The initial version and changelog are already committed. After the one-time +setup and a successful CI run on `main`, create and push its annotated tag: + +```bash +git tag -a v0.1.0 -m "Release v0.1.0" +git push origin v0.1.0 +``` + +The tag starts the Release workflow. The regular release task below handles +every subsequent version. + +## Later releases + +Install [git-cliff](https://git-cliff.org/), start from a clean and current +`main`, and choose the intended version increment: + +```bash +git switch main +git pull --ff-only +bundle exec rake 'release:prepare[patch]' +``` + +Use `minor`, `major`, or an explicit version such as `0.2.0` when appropriate. +The task updates the changelog and version constant, creates the release commit +and annotated tag, and pushes both. + +## Verify a release + +The Release workflow must complete all of the following: + +1. Pass the supported-Ruby test and style matrix. +2. Confirm that the tag matches `RubyLLM::Codex::VERSION`. +3. Find a matching `CHANGELOG.md` section. +4. Build and push the gem to RubyGems. +5. Create the GitHub release from the changelog entry. + +Afterward, verify the version on +[RubyGems](https://rubygems.org/gems/ruby_llm-codex) and install it in a clean +environment before announcing the release. + +## Failure handling + +- If CI fails before publication, fix the failure on a branch. Delete and + recreate the tag only if RubyGems does not contain that version. +- If RubyGems contains the version, never overwrite or reuse it. Fix forward + with a new patch release. +- If gem publication succeeds but GitHub release creation fails, create the + GitHub release for the existing tag using the matching changelog section. +- Never commit a RubyGems API key or add one as a repository secret for this + workflow. diff --git a/ruby_llm-codex.gemspec b/ruby_llm-codex.gemspec index 629f0ab..5ce915e 100644 --- a/ruby_llm-codex.gemspec +++ b/ruby_llm-codex.gemspec @@ -13,9 +13,9 @@ Gem::Specification.new do |spec| official Codex CLI, reuses local ChatGPT authentication, and maps RubyLLM structured-output schemas to Codex output schemas. TEXT - spec.homepage = "https://www.ethos-link.com/opensource/ruby_llm-codex" + spec.homepage = "https://github.com/ethos-link/ruby_llm-codex" spec.license = "MIT" - spec.required_ruby_version = ">= 3.1.3" + spec.required_ruby_version = ">= 3.3.0" repo = "https://github.com/ethos-link/ruby_llm-codex" branch = "main"