Skip to content

ci: add mutation testing with mutant (informational, license-gated) - #44

Open
Amayyas wants to merge 2 commits into
mainfrom
ci/mutation-testing
Open

ci: add mutation testing with mutant (informational, license-gated)#44
Amayyas wants to merge 2 commits into
mainfrom
ci/mutation-testing

Conversation

@Amayyas

@Amayyas Amayyas commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • mutant + mutant-rspec added (require: false)
  • .mutant.yml scopes subjects to Chorus::Router, Chorus::Context, Chorus::Orchestrator — the classes with real conditional logic. Deliberately excludes Chorus::Client (fully mocked in specs, see No HTTP-contract-level test for Client's request shape #9) and the thin agent classes.
  • .github/workflows/mutation.yml: continue-on-error, triggers are workflow_dispatch + weekly schedule only (not push/PR yet)
  • CONTRIBUTING.md documents local usage + the registration requirement

Closes #26

Why this needs a follow-up from you

mutant is free for public OSS use, but requires registering this repo under mutant's Free Project License at a separate account (https://github.com/mbj/mutant) — not something I can do on your behalf. Until that's done, --usage opensource will fail its license check. The workflow is continue-on-error so this won't block anything either way.

Honesty about verification

I could not fully verify this end-to-end:

  • Locally: mutant depends on parser/racc, which needs native compilation unavailable in this dev environment (same root cause as not being able to run RuboCop locally all along).
  • In CI: the license gate blocks a real run until the repo is registered.

The config was written carefully against mutant's documented schema (verified via the actual README/docs, not guessed), but the first real confirmation that it works will be whoever runs it after registering the repo.

Test plan

  • YAML syntax validated (.mutant.yml, workflow file)
  • bundle exec rspec — 34/34 passing (mutant isn't autoloaded, no interference)
  • CI green (the mutant job itself won't meaningfully run until license registration — everything else should pass)
  • Manually register the repo, then workflow_dispatch this workflow to confirm it actually works

Summary by CodeRabbit

  • Tests

    • Added automated mutation testing for core routing, context, and orchestration behavior.
    • The checks run on demand and weekly, providing informational results without blocking changes.
  • Documentation

    • Added contributor guidance for configuring and running mutation tests, including setup and licensing notes.

- mutant + mutant-rspec added, require: false (not autoloaded)
- .mutant.yml scopes subjects to Chorus::Router, Chorus::Context, and
  Chorus::Orchestrator — the classes with real conditional logic and
  behavioral specs. Deliberately excludes Chorus::Client (every spec
  mocks it entirely, see #9 — mutating unexercised HTTP code would
  just be noise) and the thin agent wrapper classes.
- .github/workflows/mutation.yml: continue-on-error, and NOT wired to
  push/pull_request yet — mutant's free Open Source License requires
  registering this repo under a separate account
  (https://github.com/mbj/mutant), which isn't automatable. Triggers
  are workflow_dispatch + weekly schedule for now; once registered and
  the runtime on this codebase is known, this can move to
  push/pull_request.
- CONTRIBUTING.md documents how to run it locally and the
  registration requirement.

Neither this nor local testing could fully verify the mutant run
end-to-end: mutant depends on parser/racc, which needs native
compilation unavailable in this dev environment, and the license gate
blocks a real CI run until the repo is registered. Config was written
carefully against mutant's documented schema, but the first real
verification will be whoever runs it after registering.

Closes #26
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The project adds Mutant dependencies and configuration for three Chorus classes. GitHub Actions runs mutation tests manually or every Monday. Contributor documentation describes local setup, licensing, and the non-blocking CI job.

Changes

Mutation testing

Layer / File(s) Summary
Mutation tooling configuration
Gemfile, .mutant.yml
The Gemfile pins mutant and mutant-rspec for Ruby 3.2. .mutant.yml configures RSpec integration and targets Chorus::Router, Chorus::Context, and Chorus::Orchestrator.
Mutation testing execution and documentation
.github/workflows/mutation.yml, CONTRIBUTING.md
GitHub Actions runs Mutant manually or every Monday at 06:00 UTC. Contributor documentation describes local execution, licensing, and the non-blocking CI status.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the addition of informational, license-gated mutation testing with Mutant.
Linked Issues check ✅ Passed The changes satisfy issue #26 by selecting Mutant, adding non-blocking CI integration, and documenting local execution.
Out of Scope Changes check ✅ Passed All changes support issue #26 and the stated mutation-testing objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/mutation-testing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

mutant/mutant-rspec 0.16.0 raised their required Ruby version to >= 3.3,
which broke bundle install on every job pinned to Ruby 3.2 (Tests 3.2,
RuboCop, bundler-audit, Gem builds) — verified in CI on this same PR.
0.15.1 is the latest release still supporting 3.2, matching this
project's required_ruby_version and CI matrix.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/mutation.yml:
- Line 20: Update the workflow permissions to grant only contents: read, and
configure the actions/checkout step to use persist-credentials: false. Apply
both changes to the checkout workflow around the existing actions/checkout@v5
step so the later bundle exec mutant command cannot inherit a writable token.

In `@CONTRIBUTING.md`:
- Line 41: Update the contributor-facing wording in CONTRIBUTING.md to hyphenate
“open source” as “open-source” when it functions as a compound adjective,
without changing the surrounding license instruction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 46f3c4c5-f006-4e90-a7cd-768f453da899

📥 Commits

Reviewing files that changed from the base of the PR and between f96ba33 and fac1bc7.

📒 Files selected for processing (4)
  • .github/workflows/mutation.yml
  • .mutant.yml
  • CONTRIBUTING.md
  • Gemfile

runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## workflow files"
git ls-files '.github/workflows/*' || true

echo "## mutation workflow"
if [ -f .github/workflows/mutation.yml ]; then
  nl -ba .github/workflows/mutation.yml | sed -n '1,180p'
fi

echo "## Gemfile indicators in mutation workflow"
rg -n "persist-credentials|permissions:|actions/checkout|bundler|bundle install|Gemfile|git |GH_TOKEN|GITHUB_TOKEN|checkout@v5|workflow_dispatch|schedule" .github/workflows/mutation.yml || true

echo "## repo Gemfiles (read-only)"
git ls-files | grep -E '(^|/)Gemfile$|\.gemspec$' || true

echo "## Gemfile git dependency occurrences"
rg -n "source.*https:/|^ *git [\"']|git\s" -g 'Gemfile*' -g '*.gemspec' . || true

Repository: Amayyas/Chorus

Length of output: 378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## workflows"
git ls-files '.github/workflows/*' || true

echo "## mutation workflow"
if [ -f .github/workflows/mutation.yml ]; then
  nl -ba .github/workflows/mutation.yml | sed -n '1,180p'
fi

echo "## relevant workflow options"
rg -nE "persist-credentials|permissions:|actions/checkout|bundler|bundle install|Gemfile|git |GH_TOKEN|GITHUB_TOKEN|workflow_dispatch|schedule" .github/workflows/mutation.yml || true

echo "## repo Gemfiles"
git ls-files | grep -E '(^|/)Gemfile$|\.gemspec$' || true

echo "## Gemfile gemspec git dependency occurrences"
rg -nE "source .*https:/|^ *git [\"']|git\s" -g 'Gemfile*' -g '*.gemspec' . || true

Repository: Amayyas/Chorus

Length of output: 373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## workflow files"
git ls-files '.github/workflows/*'

echo "## mutation workflow"
cat -n .github/workflows/mutation.yml

echo "## relevant workflow options"
grep -nE 'persist-credentials|permissions:|actions/checkout|bundler|bundle install|Gemfile|git |GH_TOKEN|GITHUB_TOKEN|workflow_dispatch|schedule' .github/workflows/mutation.yml || true

echo "## repo Gemfiles"
git ls-files | grep -E '(^|/)Gemfile$|\.gemspec$' || true

echo "## Gemfile gemspec git dependency occurrences"
grep -RInE "source .*https:/|^ *git [\"']|git\s" -g 'Gemfile*' -g '*.gemspec' . || true

Repository: Amayyas/Chorus

Length of output: 1789


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Gemfile"
cat -n Gemfile

echo "## Gemspec"
cat -n chorus-llm.gemspec

echo "## actions checkout documentation option"
python3 - <<'PY'
import urllib.request
urls = [
    "https://raw.githubusercontent.com/actions/checkout/main/README.md",
]
for url in urls:
    try:
        data = urllib.request.urlopen(url, timeout=20).read().decode("utf-8", "replace")
    except Exception as exc:
        print(f"FETCH_ERROR {url}: {exc}")
        continue
    for needle in ["persist-credentials", "token", "git config --local --unset"]:
        print(f"\n## occurrences for {needle!r} in {url}")
        for i, line in enumerate(data.splitlines(), 1):
            if needle in line:
                print(f"{i}: {line}")
    break
PY

Repository: Amayyas/Chorus

Length of output: 2386


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from urllib.request import urlopen, Request
url = "https://raw.githubusercontent.com/actions/checkout/main/README.md"
req = Request(url, headers={"User-Agent": "CodeRabbit-review/1.0"})
data = urlopen(req, timeout=20).read().decode("utf-8", "replace")
lines = data.splitlines()
print("## actions/checkout persist-credentials evidence")
for i, line in enumerate(lines, 1):
    if "persist-credentials" in line or "credential" in line.lower() or "git config --local --unset" in line:
        print(f"{i}: {line}")
PY

echo "## Gemfile git dependency occurrences"
grep -RInE "source .*https:/|^ *git [\"']|git\s" --include='Gemfile*' --include='*.gemspec' . || true

Repository: Amayyas/Chorus

Length of output: 2438


Disable checkout credential persistence.

This workflow only needs to read the repo contents and then execute bundle exec mutant; the Gemfile and gemspec use RubyGems sources with no Git dependencies. Set permissions: contents: read and configure checkout with persist-credentials: false so the later script does not inherit an actionable token by default.

🧰 Tools
🪛 zizmor (1.28.0)

[warning] 20-20: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/mutation.yml at line 20, Update the workflow permissions
to grant only contents: read, and configure the actions/checkout step to use
persist-credentials: false. Apply both changes to the checkout workflow around
the existing actions/checkout@v5 step so the later bundle exec mutant command
cannot inherit a writable token.

Source: Linters/SAST tools

Comment thread CONTRIBUTING.md
Runs [mutant](https://github.com/mbj/mutant) against the classes with real
conditional logic (`Chorus::Router`, `Chorus::Context`, `Chorus::Orchestrator`
— see `.mutant.yml`) to check whether the specs would actually catch a
regression, not just that they pass. Free for public open source use, but

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Hyphenate open-source.

Use the compound adjective in this contributor-facing license instruction.

Proposed wording fix
-Free for public open source use, but requires registering this repo once under mutant's Free Project License —
+Free for public open-source use, but requires registering this repo once under mutant's Free Project License —
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
regression, not just that they pass. Free for public open source use, but
regression, not just that they pass. Free for public open-source use, but
🧰 Tools
🪛 LanguageTool

[grammar] ~41-~41: Use a hyphen to join words.
Context: ...ust that they pass. Free for public open source use, but requires registering thi...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CONTRIBUTING.md` at line 41, Update the contributor-facing wording in
CONTRIBUTING.md to hyphenate “open source” as “open-source” when it functions as
a compound adjective, without changing the surrounding license instruction.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add mutation testing to verify test quality

1 participant