{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/mistral-ai/2026-03-11-rails-testing-on-autopilot-building-an-agent-that-writes/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "In most large Rails monoliths, organizations prioritize writing new features over writing tests for them. Over time, more and more code goes untested, forcing teams to spend more time debugging painful bugs.\n\nWe built an autonomous agent that closes that gap. It reads Rails source files, generates or improves RSpec tests, validates them against style rules and coverage targets, and runs inside a CI/CD pipeline with no human intervention. To operate on codebases at this scale, it runs in parallel: multiple instances working on different files simultaneously.\n\n## RSpec through an agent's eyes\n\nRuby is dynamically typed: there is no compilation step, so errors surface at runtime. For our agent, this means the only way to verify test syntax is to execute it. [**RSpec**](https://rspec.info/), the standard Rails testing framework, makes tests expressive and readable, but its domain-specific language is easy to get wrong.\n\nWhen the agent reads a Ruby on Rails codebase, it reads five main file types (models, serializers, controllers, mailers, helpers), each structured differently (therefore tested in different ways). The agent needs distinct instructions for each type.\n\nOne benefit: the mapping from source file to spec file is nearly 1:1. The general convention is:\n\n![Standard Mapping](https://mistral.ai/_astro/5825a803-d859-48f1-a3f8-ea095da3416a_1fscIv.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\nThere are a few exceptions to that rule however, like that `app/controllers/` are sometimes mapped to `spec/requests/`, or that sometimes a single source file can have multiple spec files, in which case the convention is:\n\n![Edge Cases](https://mistral.ai/_astro/6ba9925a-0be9-410e-ba7f-e23d961531c2_QUOiW.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\nThis straightforward mapping makes it easy to locate the tests for any given file, or to identify files that lack tests entirely.\n\nWhere it gets harder for our agent is that to avoid duplicating code, RSpec relies heavily on shared context: factories, fixtures, database schemas...\n\n- **Factories:** Reusable templates for creating test objects with predefined attributes, making it easy to generate consistent test data.\n\n- **Fixtures:** Static data files that preload test database records, providing a fixed baseline for tests.\n\nIf a factory file doesn’t exist, the agent creates it; if it does, the agent reuses it. Because factories are shared across many tests (unlike spec files), careless changes can easily break tests elsewhere, so updates to these files must be made with caution.\n\n## Building the agent with Vibe\n\nWe built the agent on top of [**Vibe**](https://mistral.ai/products/vibe), Mistral's open-source coding assistant. The default system prompt was sufficient for this project, so we focused on three levers: repository-level context, specialized skills, and custom tools.\n\n### Context engineering\n\nContext engineering was central to our approach. Vibe supports a repository-level `AGENTS.md` file: when running on a repository with this file at its root, its contents are automatically appended to the system prompt.\n\nThe `AGENTS.md` we used provided basic details about the target repositories, but mostly, it provided the agent with a **step-by-step execution plan:**\n\n```text\n1. Read the source file2. Read the documentation (if it exists)3. Check if a spec already exists4. Choose and read exactly one skill based on the source file location5. Find existing patterns, factories, and helpers6. Execute the skill (Extract → Factory → Generate tests)7. Validate with Rubocop tool8. Validate with SimpleCov tool\n```\n\nEach step included details about what to do and what the success criteria are. We also included some best practices of RSpec on areas where we felt it was important to orient the agent. Example:\n\n```text\n- **NEVER** use `be_present`, `be_truthy`, `be_between`, or `include(:key)`These are vague. Use `eq(exact_value)` always\n```\n\nWe found the agent would sometimes skip methods or leave edge cases untested: it would generate a spec that *looked* complete but quietly ignored a few public methods from the source file. To counter this, the `AGENTS.md` ends with a forced self-review: the agent must re-read the source file and explicitly ask itself \"Did I test every public method? Count them.\" before finishing. If anything is missing, it goes back.\n\nWith this generic `AGENTS.md` file forcing the agent to follow strict planning, our quality score went from **0.68 to 0.74**, all from a single markdown file with framework-level instructions.\n\n### Using SKILLS files:\n\nRecall step 4 of our `AGENTS.md`:\n\n`4. Choose and read exactly one skill based on the source file location`\n\nA single generic skill would produce mediocre results: the instructions precise enough for testing a model file are the wrong instructions for a controller file.\n\nWhat worked was creating a separate skills file for each category, plus one for plain Ruby files.\n\nHere is an example of a basic skills file for testing controllers:\n\n```text\n---name: \"Generate Request Spec\"description: \"Generate RSpec request tests for a Rails controller. Use when the source file is in app/controllers/.\"---\n# Generate Request Spec\n## File Scope\n- `spec/requests//_spec.rb` — drop `_controller` from the filename- `spec/factories/.rb` — create or update if needed\n## Example tests for Controllers\n# frozen_string_literal: truerequire 'rails_helper'\ndescribe 'Admin::Users', type: :request do  let(:user) { create(:user, :admin) }  before { sign_in user }\n  # Unauthorized access — one test per action  describe '#authorized?' do    let(:user) { create(:user) }    it 'GET /admin/users redirects' do      get '/admin/users'      expect(response).to have_http_status(:redirect)    end  end\n  # Each action: happy + sad paths  describe 'POST /admin/users' do    let(:valid_params)   { { user: attributes_for(:user) } }    let(:invalid_params) { { user: { email: '' } } }\n    context 'with valid params' do      it 'creates a record' do        expect { post '/admin/users', params: valid_params }.to change(User, :count).by(1)        expect(response).to have_http_status(:created)      end    end\n    context 'with invalid params' do      it 'returns unprocessable entity with errors' do        post '/admin/users', params: invalid_params        expect(response).to have_http_status(:unprocessable_entity)        json = JSON.parse(response.body, symbolize_names: true)        expect(json[:errors]).to include(\"Email can't be blank\")      end    end  endend\n## Critical Rules\n- **Assert content, not just status:** always parse JSON and verify exact values- **Test exact error messages:** `include(\"Email can't be blank\")`, not `be_present`- **Verify state changes:** use `.by(1)` and check the created record's attributes- **Cover all actions:** index, show, create, update, destroy, and any custom actions- **Test auth on every action:** verify unauthorized users get the correct status code- Use `let` only, never `@instance_variables`; `sign_in user` again before `follow_redirect!`\n```\n\n### Adding custom tools\n\nVibe has built-in tools for reading and writing files, running bash commands, and editing code. But it also supports custom tools & MCPs to expand its action space. For this project, custom tools were key.\n\nAfter some experimentation, we went for:\n\n- A [RuboCop](https://rubocop.org/) linting tool that runs the `rubocop` command to detect style violations, so that the agent goes back to the code and fixes them.\n\n- A [SimpleCov](https://github.com/simplecov-ruby/simplecov) tool integrated with RSpec that checks both code coverage and test correctness. Placing this as the final step was key: it guarantees every test the agent writes actually runs.\n\nBelow is a stripped-down version of how we implemented the Rubocop linting tool:\n\n```python\nimport asynciofrom pathlib import Path\nfrom pydantic import BaseModelfrom vibe.core.tools.base import BaseTool, ToolError\nclass RubocopLintResult(BaseModel):    command: str    output: str    success: bool    exit_code: int | None = None\nclass RubocopLint(BaseTool):    description = \"Lint spec files using Rubocop.\"\n    async def run(self, spec_path: str):        spec_path = Path(spec_path)\n        cmd = [\"rubocop\", \"--format\", \"simple\", str(spec_path)]        proc = await asyncio.create_subprocess_exec(            *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE        )\n        try:            stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=60)        except TimeoutError:            proc.kill()            await proc.wait()            raise ToolError(\"Rubocop timed out after 60s\")\n        output = stdout.decode(\"utf-8\", errors=\"ignore\")\n        yield RubocopLintResult(            command=\" \".join(cmd),            output=output,            success=proc.returncode == 0,            exit_code=proc.returncode        )\n```\n\nThe SimpleCov tool follows the same pattern but does more: it runs the spec file with RSpec, collects pass/fail results for each test, and reports the coverage percentage of the source file. The agent gets back which tests failed (with error messages), and which lines of the source file were never exercised. This gives it enough to self-correct when needed.\n\nWhen we first wired this tool in, only around a third of generated tests passed on first execution. The agent self-corrected all failures within a few iterations. Without this step, some of those tests would not have ran.\n\n## Measuring test quality\n\nA good unit test follows the [**Arrange-Act-Assert**](https://semaphore.io/blog/aaa-pattern-test-automation) pattern: set up state, execute one behavior, verify the outcome. It should use precise assertions, cover both happy and error paths, and produce failures that clearly identify what broke.\n\n![Good Testing](https://mistral.ai/_astro/32542f94-4778-4112-820f-203e96ef0735_Z1IC6ad.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\n### Tool-based metrics\n\nExisting Ruby on Rails tools allow us to get several metrics easily:\n\n- **RSpec:** the most popular testing framework for Ruby. Expressive syntax for writing readable, well-structured tests.\n\n- **RuboCop:** a static Ruby code analyzer and formatter, helping enforce style guidelines and keeping code consistent\n\n- **SimpleCov:** a code coverage tool for Ruby, measuring how much code is executed by the test suite.\n\nFrom these tools, we can extract several useful quantitative metrics: the proportion of tests that pass, the number of RuboCop violations per file, the proportion of code covered by the unit test suite, and the speed of execution. These metrics provide a solid baseline for measuring the overall performance of a test suite.\n\nBut these numbers alone cannot determine whether a test file is actually good. How should we compare two files that both have zero RuboCop violations, 100% coverage, and all tests passing? The quantitative metrics may look identical, yet qualitative differences in test quality can still exist.\n\n### LLM-as-a-judge\n\nThe idea behind LLM-as-a-judge is to ask an LLM: “Are all error conditions tested?” or “Is this a good test?” and get back a score from 0 to 1 based on clear criteria.\n\nExample prompt:\n\n```text\n## Good Tests\nPrecise values: `eq(100)`, not `be_present` or `be_a(Float)`.State changes: `change { User.count }.by(1)`.Error paths: `raise_error(ArgumentError, \"message\")`.Boundary cases: Test exact values at thresholds (e.g., `page(0)`).Public interface: No `.send(:private_method)`.\n## Bad Tests\nVague assertions: `be_truthy`, `be_an(Array)`.\"Doesn't crash\" tests: `not_to raise_error`.Testing private methods.Over-mocking (tests mocks, not behavior).No assertions on return values.\n## Scoring Rules\n- 1.0: Precise assertions on return values/state + all key paths covered (happy, error, boundaries).- 0.8: Precise assertions on all tested paths, but 1-2 edge cases missing entirely.- 0.6: All key paths covered, but 1-2 tests use vague assertions (e.g., `be_truthy` instead of `eq(expected)`) or minor over-mocking.- 0.4: Some tests have precise assertions; others are vague, over-mocked, or skip return value checks.- 0.2: Most tests lack meaningful assertions: vague matchers, stubbed-out logic, no verification of actual output.- 0.0: Tests are structurally broken: testing private methods, no real assertions, or verifying mocks instead of behavior.\n```\n\nThe \"Scoring Rules\" section here is especially important as it enforces the score to be more consistent across runs. Without it, the agent would lack clear definition of how strict or how lax the scoring is.\n\nSuch a score is less authoritative than a tool's output or a thorough developer review. But it is a good indication of how well the current version of our agent is doing, especially when run at scale across every file in a repository.\n\n#### Limitations of LLM-as-a-judge evaluation:\n\nAnything that can be put into text can be scored with LLM-as-a-judge, which makes it versatile. But the score output is not deterministic. Even with *temperature=0*, there will be variance due to the order of kernel operations. In practice the outputs for the same evaluation are always close, but this lack of determinism can be an issue for some use cases.\n\nFor our use case, since we score not a single test but usually tens to hundreds at a time, this was an acceptable limitation. The aggregated score stayed consistent.\n\n### The missing parenthesis problem\n\nThere was another core problem with LLM-as-a-judge: a test can be of great quality but not run, or have the logic behind it be wrong altogether.\n\nExample:\n\n```text\nRSpec.describe User, \"#full_name\" do  it \"combines first and last name with a space\" do    user = User.new(first_name: \"Ada\", last_name: \"Lovelace\"\n    expect(user.full_name).to eq(\"Ada Lovelace\")  endend\n```\n\nThis test looks great. It tests one behavior, has a clear description that reads as documentation, follows [**Arrange-Act-Assert**](https://semaphore.io/blog/aaa-pattern-test-automation):, and would fail with a useful message if broken.\n\nWe ran this test through multiple models using the scoring prompt defined earlier. The average score given was 0.75. A developer taking a quick look would likely agree.\n\nBut this test has one core flaw: **it will not run.** All because of a missing closing parenthesis `)` on the `User.new()`. This is a critical flaw that makes this test definitely not suitable.\n\nThis example is easy to mitigate. But there are a lot other possibilities that can lead to syntax error:\n\n- **Versioning errors:** Using methods or syntax from an incompatible Ruby or Rails version\n\n- **Non-existent methods:** some methods might have been removed, renamed, or never existed.\n\n- **Factory/fixture errors:** Referencing missing factories, incorrect attributes, or missing associations\n\n- **Missing dependencies:** Using gems or modules not included in the test environment\n\n## The experiment\n\nWe ran an experiment to check the agent could hold up against a real codebase. The target repository contained 275 source files. Half of them already had test coverage. The other half had none.\n\nWe pointed the agent at every file, whether it already had tests or not. For uncovered files, it generated specs from scratch successfully. For files that already had tests, it rewrote and improved them.\n\nWe scored every spec file before and after with LLM-as-a-judge. The aggregate score for tested files went from 0.49 to 0.74, and coverage reached 100%. There's still room for improvement, but the results validated the approach.\n\n| Metric                                   | Result |\n| ---------------------------------------- | ------ |\n| Files processed                          | 275    |\n| Tests passing                            | 100%   |\n| Average line coverage (SimpleCov)        | 100%   |\n| RuboCop violations after self-correction | 0      |\n| LLM-as-a-judge score                     | 0.74   |\n\n### By file type\n\nNot all file types were easy to test. Models scored highest: their business logic is self-contained and the test patterns are predictable. Controllers were harder, HTTP request handling introduced more room for error.\n\n| File type   | LLM-as-a-judge |\n| ----------- | -------------- |\n| Models      | 0.81           |\n| Controllers | 0.67           |\n| Serializers | 0.80           |\n\n### Does it run?\n\nBundling SimpleCov with RSpec execution as the final step, forcing the agent to run every test it wrote, was the single most impactful decision. Only a third of tests passed on first execution. The agent self-corrected all failures within a few iterations.\n\nWithout this step, LLM-as-a-judge would have rated these non-running tests as high quality: the missing parenthesis problem at scale.\n\n[**Vibe is open source**](https://github.com/mistralai/mistral-vibe). The agent, tools, and skills described here all run on top of it. Try it now.\n",
  "authors": [],
  "captured_at": "2026-10-19T01:26:53.998049939+00:00",
  "category": "ai",
  "content_html": "<p>In most large Rails monoliths, organizations prioritize writing new features over writing tests for them. Over time, more and more code goes untested, forcing teams to spend more time debugging painful bugs.</p>\n<p>We built an autonomous agent that closes that gap. It reads Rails source files, generates or improves RSpec tests, validates them against style rules and coverage targets, and runs inside a CI/CD pipeline with no human intervention. To operate on codebases at this scale, it runs in parallel: multiple instances working on different files simultaneously.</p>\n<h2>RSpec through an agent's eyes</h2>\n<p>Ruby is dynamically typed: there is no compilation step, so errors surface at runtime. For our agent, this means the only way to verify test syntax is to execute it. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://rspec.info/\"><strong>RSpec</strong></a>, the standard Rails testing framework, makes tests expressive and readable, but its domain-specific language is easy to get wrong.</p>\n<p>When the agent reads a Ruby on Rails codebase, it reads five main file types (models, serializers, controllers, mailers, helpers), each structured differently (therefore tested in different ways). The agent needs distinct instructions for each type.</p>\n<p>One benefit: the mapping from source file to spec file is nearly 1:1. The general convention is:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://mistral.ai/_astro/5825a803-d859-48f1-a3f8-ea095da3416a_1fscIv.webp?dpl=6ad1d7b6946342e26a4d2b4f\" alt=\"Standard Mapping\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>There are a few exceptions to that rule however, like that <code>app/controllers/</code> are sometimes mapped to <code>spec/requests/</code>, or that sometimes a single source file can have multiple spec files, in which case the convention is:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://mistral.ai/_astro/6ba9925a-0be9-410e-ba7f-e23d961531c2_QUOiW.webp?dpl=6ad1d7b6946342e26a4d2b4f\" alt=\"Edge Cases\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>This straightforward mapping makes it easy to locate the tests for any given file, or to identify files that lack tests entirely.</p>\n<p>Where it gets harder for our agent is that to avoid duplicating code, RSpec relies heavily on shared context: factories, fixtures, database schemas...</p>\n<ul>\n<li>\n<p><strong>Factories:</strong> Reusable templates for creating test objects with predefined attributes, making it easy to generate consistent test data.</p>\n</li>\n<li>\n<p><strong>Fixtures:</strong> Static data files that preload test database records, providing a fixed baseline for tests.</p>\n</li>\n</ul>\n<p>If a factory file doesn’t exist, the agent creates it; if it does, the agent reuses it. Because factories are shared across many tests (unlike spec files), careless changes can easily break tests elsewhere, so updates to these files must be made with caution.</p>\n<h2>Building the agent with Vibe</h2>\n<p>We built the agent on top of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://mistral.ai/products/vibe\"><strong>Vibe</strong></a>, Mistral's open-source coding assistant. The default system prompt was sufficient for this project, so we focused on three levers: repository-level context, specialized skills, and custom tools.</p>\n<h3>Context engineering</h3>\n<p>Context engineering was central to our approach. Vibe supports a repository-level <code>AGENTS.md</code> file: when running on a repository with this file at its root, its contents are automatically appended to the system prompt.</p>\n<p>The <code>AGENTS.md</code> we used provided basic details about the target repositories, but mostly, it provided the agent with a <strong>step-by-step execution plan:</strong></p>\n<pre><code class=\"language-text\"><span class=\"code-snippet\">1. Read the source file2. Read the documentation (if it exists)3. Check if a spec already exists4. Choose and read exactly one skill based on the source file location5. Find existing patterns, factories, and helpers6. Execute the skill (Extract → Factory → Generate tests)7. Validate with Rubocop tool8. Validate with SimpleCov tool\n</span></code></pre>\n<p>Each step included details about what to do and what the success criteria are. We also included some best practices of RSpec on areas where we felt it was important to orient the agent. Example:</p>\n<pre><code class=\"language-text\"><span class=\"code-snippet\">- **NEVER** use `be_present`, `be_truthy`, `be_between`, or `include(:key)`These are vague. Use `eq(exact_value)` always\n</span></code></pre>\n<p>We found the agent would sometimes skip methods or leave edge cases untested: it would generate a spec that <em>looked</em> complete but quietly ignored a few public methods from the source file. To counter this, the <code>AGENTS.md</code> ends with a forced self-review: the agent must re-read the source file and explicitly ask itself &quot;Did I test every public method? Count them.&quot; before finishing. If anything is missing, it goes back.</p>\n<p>With this generic <code>AGENTS.md</code> file forcing the agent to follow strict planning, our quality score went from <strong>0.68 to 0.74</strong>, all from a single markdown file with framework-level instructions.</p>\n<h3>Using SKILLS files:</h3>\n<p>Recall step 4 of our <code>AGENTS.md</code>:</p>\n<p><code>4. Choose and read exactly one skill based on the source file location</code></p>\n<p>A single generic skill would produce mediocre results: the instructions precise enough for testing a model file are the wrong instructions for a controller file.</p>\n<p>What worked was creating a separate skills file for each category, plus one for plain Ruby files.</p>\n<p>Here is an example of a basic skills file for testing controllers:</p>\n<pre><code class=\"language-text\"><span class=\"code-snippet\">---name: &quot;Generate Request Spec&quot;description: &quot;Generate RSpec request tests for a Rails controller. Use when the source file is in app/controllers/.&quot;---\n# Generate Request Spec\n## File Scope\n- `spec/requests//_spec.rb` — drop `_controller` from the filename- `spec/factories/.rb` — create or update if needed\n## Example tests for Controllers\n# frozen_string_literal: truerequire &#39;rails_helper&#39;\ndescribe &#39;Admin::Users&#39;, type: :request do  let(:user) { create(:user, :admin) }  before { sign_in user }\n  # Unauthorized access — one test per action  describe &#39;#authorized?&#39; do    let(:user) { create(:user) }    it &#39;GET /admin/users redirects&#39; do      get &#39;/admin/users&#39;      expect(response).to have_http_status(:redirect)    end  end\n  # Each action: happy + sad paths  describe &#39;POST /admin/users&#39; do    let(:valid_params)   { { user: attributes_for(:user) } }    let(:invalid_params) { { user: { email: &#39;&#39; } } }\n    context &#39;with valid params&#39; do      it &#39;creates a record&#39; do        expect { post &#39;/admin/users&#39;, params: valid_params }.to change(User, :count).by(1)        expect(response).to have_http_status(:created)      end    end\n    context &#39;with invalid params&#39; do      it &#39;returns unprocessable entity with errors&#39; do        post &#39;/admin/users&#39;, params: invalid_params        expect(response).to have_http_status(:unprocessable_entity)        json = JSON.parse(response.body, symbolize_names: true)        expect(json[:errors]).to include(&quot;Email can&#39;t be blank&quot;)      end    end  endend\n## Critical Rules\n- **Assert content, not just status:** always parse JSON and verify exact values- **Test exact error messages:** `include(&quot;Email can&#39;t be blank&quot;)`, not `be_present`- **Verify state changes:** use `.by(1)` and check the created record&#39;s attributes- **Cover all actions:** index, show, create, update, destroy, and any custom actions- **Test auth on every action:** verify unauthorized users get the correct status code- Use `let` only, never `@instance_variables`; `sign_in user` again before `follow_redirect!`\n</span></code></pre>\n<h3>Adding custom tools</h3>\n<p>Vibe has built-in tools for reading and writing files, running bash commands, and editing code. But it also supports custom tools &amp; MCPs to expand its action space. For this project, custom tools were key.</p>\n<p>After some experimentation, we went for:</p>\n<ul>\n<li>\n<p>A <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://rubocop.org/\">RuboCop</a> linting tool that runs the <code>rubocop</code> command to detect style violations, so that the agent goes back to the code and fixes them.</p>\n</li>\n<li>\n<p>A <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/simplecov-ruby/simplecov\">SimpleCov</a> tool integrated with RSpec that checks both code coverage and test correctness. Placing this as the final step was key: it guarantees every test the agent writes actually runs.</p>\n</li>\n</ul>\n<p>Below is a stripped-down version of how we implemented the Rubocop linting tool:</p>\n<pre><code class=\"language-python\"><span class=\"code-snippet\" data-language=\"Python\"><span class=\"syntax-source syntax-python\"><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-keyword syntax-control syntax-import syntax-python\">import</span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">asynciofrom</span></span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">pathlib</span></span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-invalid syntax-illegal syntax-name syntax-python\">import</span></span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">Path</span></span></span>\n<span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-keyword syntax-control syntax-import syntax-from syntax-python\">from</span></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-meta syntax-import-source syntax-python\"> <span class=\"syntax-meta syntax-import-path syntax-python\"><span class=\"syntax-meta syntax-import-name syntax-python\">pydantic</span></span> <span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-keyword syntax-control syntax-import syntax-python\">import</span></span></span></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"> <span class=\"syntax-meta syntax-generic-name syntax-python\">BaseModelfrom</span> <span class=\"syntax-meta syntax-generic-name syntax-python\">vibe</span><span class=\"syntax-invalid syntax-illegal syntax-name syntax-import syntax-python\">.core.tools.base</span> <span class=\"syntax-invalid syntax-illegal syntax-name syntax-python\">import</span> <span class=\"syntax-meta syntax-generic-name syntax-python\">BaseTool</span><span class=\"syntax-punctuation syntax-separator syntax-import-list syntax-python\">,</span> <span class=\"syntax-meta syntax-generic-name syntax-python\">ToolError</span></span>\n<span class=\"syntax-meta syntax-class syntax-python\"><span class=\"syntax-storage syntax-type syntax-class syntax-python\">class</span> <span class=\"syntax-entity syntax-name syntax-class syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">RubocopLintResult</span></span><span class=\"syntax-meta syntax-class syntax-inheritance syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-inheritance syntax-begin syntax-python\">(</span></span></span><span class=\"syntax-meta syntax-class syntax-inheritance syntax-python\"><span class=\"syntax-entity syntax-other syntax-inherited-class syntax-python\">BaseModel</span><span class=\"syntax-punctuation syntax-section syntax-inheritance syntax-end syntax-python\">)</span></span><span class=\"syntax-meta syntax-class syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-class syntax-begin syntax-python\">:</span></span>    <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">command</span></span><span class=\"syntax-punctuation syntax-separator syntax-annotation syntax-variable syntax-python\">:</span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-support syntax-type syntax-python\">str</span></span>    <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">output</span></span><span class=\"syntax-punctuation syntax-separator syntax-annotation syntax-variable syntax-python\">:</span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-support syntax-type syntax-python\">str</span></span>    <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">success</span></span><span class=\"syntax-punctuation syntax-separator syntax-annotation syntax-variable syntax-python\">:</span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-support syntax-type syntax-python\">bool</span></span>    <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">exit_code</span></span><span class=\"syntax-punctuation syntax-separator syntax-annotation syntax-variable syntax-python\">:</span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-support syntax-type syntax-python\">int</span></span> <span class=\"syntax-keyword syntax-operator syntax-arithmetic syntax-python\">|</span> <span class=\"syntax-constant syntax-language syntax-python\">None</span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-constant syntax-language syntax-python\">None</span>\n<span class=\"syntax-meta syntax-class syntax-python\"><span class=\"syntax-storage syntax-type syntax-class syntax-python\">class</span> <span class=\"syntax-entity syntax-name syntax-class syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">RubocopLint</span></span><span class=\"syntax-meta syntax-class syntax-inheritance syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-inheritance syntax-begin syntax-python\">(</span></span></span><span class=\"syntax-meta syntax-class syntax-inheritance syntax-python\"><span class=\"syntax-entity syntax-other syntax-inherited-class syntax-python\">BaseTool</span><span class=\"syntax-punctuation syntax-section syntax-inheritance syntax-end syntax-python\">)</span></span><span class=\"syntax-meta syntax-class syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-class syntax-begin syntax-python\">:</span></span>    <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">description</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">Lint spec files using Rubocop.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span>\n<span class=\"syntax-meta syntax-function syntax-python\">    <span class=\"syntax-storage syntax-modifier syntax-async syntax-python\">async</span> <span class=\"syntax-storage syntax-type syntax-function syntax-python\">def</span> <span class=\"syntax-entity syntax-name syntax-function syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">run</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-parameters syntax-begin syntax-python\">(</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-python\"><span class=\"syntax-variable syntax-parameter syntax-python\">self</span><span class=\"syntax-punctuation syntax-separator syntax-parameters syntax-python\">,</span> <span class=\"syntax-variable syntax-parameter syntax-python\">spec_path</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-annotation syntax-python\"><span class=\"syntax-punctuation syntax-separator syntax-annotation syntax-parameter syntax-python\">:</span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-support syntax-type syntax-python\">str</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-parameters syntax-end syntax-python\">)</span></span><span class=\"syntax-meta syntax-function syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-function syntax-begin syntax-python\">:</span></span>        <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">spec_path</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">Path</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">spec_path</span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>\n        <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">cmd</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-meta syntax-structure syntax-list syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-list syntax-begin syntax-python\">[</span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">rubocop<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-list syntax-python\">,</span> <span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">--format<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-list syntax-python\">,</span> <span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">simple<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-list syntax-python\">,</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-support syntax-type syntax-python\">str</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">spec_path</span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span><span class=\"syntax-punctuation syntax-section syntax-list syntax-end syntax-python\">]</span></span>        <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">proc</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-keyword syntax-other syntax-await syntax-python\">await</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">asyncio</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span></span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">create_subprocess_exec</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\">            <span class=\"syntax-keyword syntax-operator syntax-arithmetic syntax-python\">*</span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">cmd</span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span> <span class=\"syntax-variable syntax-parameter syntax-python\">stdout</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">asyncio</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span><span class=\"syntax-meta syntax-generic-name syntax-python\">subprocess</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span><span class=\"syntax-variable syntax-other syntax-constant syntax-python\">PIPE</span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span> <span class=\"syntax-variable syntax-parameter syntax-python\">stderr</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">asyncio</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span><span class=\"syntax-meta syntax-generic-name syntax-python\">subprocess</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span><span class=\"syntax-variable syntax-other syntax-constant syntax-python\">PIPE</span></span>        </span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>\n        <span class=\"syntax-meta syntax-statement syntax-try syntax-python\"><span class=\"syntax-keyword syntax-control syntax-flow syntax-try syntax-python\">try</span><span class=\"syntax-punctuation syntax-section syntax-block syntax-try syntax-python\">:</span></span>            <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">stdout</span></span>, <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">stderr</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-keyword syntax-other syntax-await syntax-python\">await</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">asyncio</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span></span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">wait_for</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">proc</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span></span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">communicate</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span> <span class=\"syntax-variable syntax-parameter syntax-python\">timeout</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-python\">60</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>        <span class=\"syntax-meta syntax-statement syntax-except syntax-python\"><span class=\"syntax-keyword syntax-control syntax-flow syntax-except syntax-python\">except</span> <span class=\"syntax-support syntax-type syntax-exception syntax-python\">TimeoutError</span><span class=\"syntax-punctuation syntax-section syntax-block syntax-except syntax-python\">:</span></span>            <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">proc</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span></span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">kill</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>            <span class=\"syntax-keyword syntax-other syntax-await syntax-python\">await</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">proc</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span></span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">wait</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>            <span class=\"syntax-meta syntax-statement syntax-raise syntax-python\"><span class=\"syntax-keyword syntax-control syntax-flow syntax-raise syntax-python\">raise</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">ToolError</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">Rubocop timed out after 60s<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span></span>\n        <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">output</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">stdout</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span></span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">decode</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">utf-8<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span> <span class=\"syntax-variable syntax-parameter syntax-python\">errors</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">ignore<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>\n        <span class=\"syntax-keyword syntax-control syntax-flow syntax-yield syntax-python\">yield</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">RubocopLintResult</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\">            <span class=\"syntax-variable syntax-parameter syntax-python\">command</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"> <span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span></span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">join</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">cmd</span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>            <span class=\"syntax-variable syntax-parameter syntax-python\">output</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">output</span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>            <span class=\"syntax-variable syntax-parameter syntax-python\">success</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">proc</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span><span class=\"syntax-meta syntax-generic-name syntax-python\">returncode</span></span> <span class=\"syntax-keyword syntax-operator syntax-comparison syntax-python\">==</span> <span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-python\">0</span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>            <span class=\"syntax-variable syntax-parameter syntax-python\">exit_code</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">proc</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span><span class=\"syntax-meta syntax-generic-name syntax-python\">returncode</span></span>        </span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>\n</span></span></code></pre>\n<p>The SimpleCov tool follows the same pattern but does more: it runs the spec file with RSpec, collects pass/fail results for each test, and reports the coverage percentage of the source file. The agent gets back which tests failed (with error messages), and which lines of the source file were never exercised. This gives it enough to self-correct when needed.</p>\n<p>When we first wired this tool in, only around a third of generated tests passed on first execution. The agent self-corrected all failures within a few iterations. Without this step, some of those tests would not have ran.</p>\n<h2>Measuring test quality</h2>\n<p>A good unit test follows the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://semaphore.io/blog/aaa-pattern-test-automation\"><strong>Arrange-Act-Assert</strong></a> pattern: set up state, execute one behavior, verify the outcome. It should use precise assertions, cover both happy and error paths, and produce failures that clearly identify what broke.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://mistral.ai/_astro/32542f94-4778-4112-820f-203e96ef0735_Z1IC6ad.webp?dpl=6ad1d7b6946342e26a4d2b4f\" alt=\"Good Testing\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<h3>Tool-based metrics</h3>\n<p>Existing Ruby on Rails tools allow us to get several metrics easily:</p>\n<ul>\n<li>\n<p><strong>RSpec:</strong> the most popular testing framework for Ruby. Expressive syntax for writing readable, well-structured tests.</p>\n</li>\n<li>\n<p><strong>RuboCop:</strong> a static Ruby code analyzer and formatter, helping enforce style guidelines and keeping code consistent</p>\n</li>\n<li>\n<p><strong>SimpleCov:</strong> a code coverage tool for Ruby, measuring how much code is executed by the test suite.</p>\n</li>\n</ul>\n<p>From these tools, we can extract several useful quantitative metrics: the proportion of tests that pass, the number of RuboCop violations per file, the proportion of code covered by the unit test suite, and the speed of execution. These metrics provide a solid baseline for measuring the overall performance of a test suite.</p>\n<p>But these numbers alone cannot determine whether a test file is actually good. How should we compare two files that both have zero RuboCop violations, 100% coverage, and all tests passing? The quantitative metrics may look identical, yet qualitative differences in test quality can still exist.</p>\n<h3>LLM-as-a-judge</h3>\n<p>The idea behind LLM-as-a-judge is to ask an LLM: “Are all error conditions tested?” or “Is this a good test?” and get back a score from 0 to 1 based on clear criteria.</p>\n<p>Example prompt:</p>\n<pre><code class=\"language-text\"><span class=\"code-snippet\">## Good Tests\nPrecise values: `eq(100)`, not `be_present` or `be_a(Float)`.State changes: `change { User.count }.by(1)`.Error paths: `raise_error(ArgumentError, &quot;message&quot;)`.Boundary cases: Test exact values at thresholds (e.g., `page(0)`).Public interface: No `.send(:private_method)`.\n## Bad Tests\nVague assertions: `be_truthy`, `be_an(Array)`.&quot;Doesn&#39;t crash&quot; tests: `not_to raise_error`.Testing private methods.Over-mocking (tests mocks, not behavior).No assertions on return values.\n## Scoring Rules\n- 1.0: Precise assertions on return values/state + all key paths covered (happy, error, boundaries).- 0.8: Precise assertions on all tested paths, but 1-2 edge cases missing entirely.- 0.6: All key paths covered, but 1-2 tests use vague assertions (e.g., `be_truthy` instead of `eq(expected)`) or minor over-mocking.- 0.4: Some tests have precise assertions; others are vague, over-mocked, or skip return value checks.- 0.2: Most tests lack meaningful assertions: vague matchers, stubbed-out logic, no verification of actual output.- 0.0: Tests are structurally broken: testing private methods, no real assertions, or verifying mocks instead of behavior.\n</span></code></pre>\n<p>The &quot;Scoring Rules&quot; section here is especially important as it enforces the score to be more consistent across runs. Without it, the agent would lack clear definition of how strict or how lax the scoring is.</p>\n<p>Such a score is less authoritative than a tool's output or a thorough developer review. But it is a good indication of how well the current version of our agent is doing, especially when run at scale across every file in a repository.</p>\n<h4>Limitations of LLM-as-a-judge evaluation:</h4>\n<p>Anything that can be put into text can be scored with LLM-as-a-judge, which makes it versatile. But the score output is not deterministic. Even with <em>temperature=0</em>, there will be variance due to the order of kernel operations. In practice the outputs for the same evaluation are always close, but this lack of determinism can be an issue for some use cases.</p>\n<p>For our use case, since we score not a single test but usually tens to hundreds at a time, this was an acceptable limitation. The aggregated score stayed consistent.</p>\n<h3>The missing parenthesis problem</h3>\n<p>There was another core problem with LLM-as-a-judge: a test can be of great quality but not run, or have the logic behind it be wrong altogether.</p>\n<p>Example:</p>\n<pre><code class=\"language-text\"><span class=\"code-snippet\">RSpec.describe User, &quot;#full_name&quot; do  it &quot;combines first and last name with a space&quot; do    user = User.new(first_name: &quot;Ada&quot;, last_name: &quot;Lovelace&quot;\n    expect(user.full_name).to eq(&quot;Ada Lovelace&quot;)  endend\n</span></code></pre>\n<p>This test looks great. It tests one behavior, has a clear description that reads as documentation, follows <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://semaphore.io/blog/aaa-pattern-test-automation\"><strong>Arrange-Act-Assert</strong></a>:, and would fail with a useful message if broken.</p>\n<p>We ran this test through multiple models using the scoring prompt defined earlier. The average score given was 0.75. A developer taking a quick look would likely agree.</p>\n<p>But this test has one core flaw: <strong>it will not run.</strong> All because of a missing closing parenthesis <code>)</code> on the <code>User.new()</code>. This is a critical flaw that makes this test definitely not suitable.</p>\n<p>This example is easy to mitigate. But there are a lot other possibilities that can lead to syntax error:</p>\n<ul>\n<li>\n<p><strong>Versioning errors:</strong> Using methods or syntax from an incompatible Ruby or Rails version</p>\n</li>\n<li>\n<p><strong>Non-existent methods:</strong> some methods might have been removed, renamed, or never existed.</p>\n</li>\n<li>\n<p><strong>Factory/fixture errors:</strong> Referencing missing factories, incorrect attributes, or missing associations</p>\n</li>\n<li>\n<p><strong>Missing dependencies:</strong> Using gems or modules not included in the test environment</p>\n</li>\n</ul>\n<h2>The experiment</h2>\n<p>We ran an experiment to check the agent could hold up against a real codebase. The target repository contained 275 source files. Half of them already had test coverage. The other half had none.</p>\n<p>We pointed the agent at every file, whether it already had tests or not. For uncovered files, it generated specs from scratch successfully. For files that already had tests, it rewrote and improved them.</p>\n<p>We scored every spec file before and after with LLM-as-a-judge. The aggregate score for tested files went from 0.49 to 0.74, and coverage reached 100%. There's still room for improvement, but the results validated the approach.</p>\n<table>\n<thead>\n<tr>\n<th>Metric</th>\n<th>Result</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Files processed</td>\n<td>275</td>\n</tr>\n<tr>\n<td>Tests passing</td>\n<td>100%</td>\n</tr>\n<tr>\n<td>Average line coverage (SimpleCov)</td>\n<td>100%</td>\n</tr>\n<tr>\n<td>RuboCop violations after self-correction</td>\n<td>0</td>\n</tr>\n<tr>\n<td>LLM-as-a-judge score</td>\n<td>0.74</td>\n</tr>\n</tbody>\n</table>\n<h3>By file type</h3>\n<p>Not all file types were easy to test. Models scored highest: their business logic is self-contained and the test patterns are predictable. Controllers were harder, HTTP request handling introduced more room for error.</p>\n<table>\n<thead>\n<tr>\n<th>File type</th>\n<th>LLM-as-a-judge</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Models</td>\n<td>0.81</td>\n</tr>\n<tr>\n<td>Controllers</td>\n<td>0.67</td>\n</tr>\n<tr>\n<td>Serializers</td>\n<td>0.80</td>\n</tr>\n</tbody>\n</table>\n<h3>Does it run?</h3>\n<p>Bundling SimpleCov with RSpec execution as the final step, forcing the agent to run every test it wrote, was the single most impactful decision. Only a third of tests passed on first execution. The agent self-corrected all failures within a few iterations.</p>\n<p>Without this step, LLM-as-a-judge would have rated these non-running tests as high quality: the missing parenthesis problem at scale.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mistralai/mistral-vibe\"><strong>Vibe is open source</strong></a>. The agent, tools, and skills described here all run on top of it. Try it now.</p>\n",
  "content_markdown": "In most large Rails monoliths, organizations prioritize writing new features over writing tests for them. Over time, more and more code goes untested, forcing teams to spend more time debugging painful bugs.\n\nWe built an autonomous agent that closes that gap. It reads Rails source files, generates or improves RSpec tests, validates them against style rules and coverage targets, and runs inside a CI/CD pipeline with no human intervention. To operate on codebases at this scale, it runs in parallel: multiple instances working on different files simultaneously.\n\n## RSpec through an agent's eyes\n\nRuby is dynamically typed: there is no compilation step, so errors surface at runtime. For our agent, this means the only way to verify test syntax is to execute it. [**RSpec**](https://rspec.info/), the standard Rails testing framework, makes tests expressive and readable, but its domain-specific language is easy to get wrong.\n\nWhen the agent reads a Ruby on Rails codebase, it reads five main file types (models, serializers, controllers, mailers, helpers), each structured differently (therefore tested in different ways). The agent needs distinct instructions for each type.\n\nOne benefit: the mapping from source file to spec file is nearly 1:1. The general convention is:\n\n![Standard Mapping](https://mistral.ai/_astro/5825a803-d859-48f1-a3f8-ea095da3416a_1fscIv.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\nThere are a few exceptions to that rule however, like that `app/controllers/` are sometimes mapped to `spec/requests/`, or that sometimes a single source file can have multiple spec files, in which case the convention is:\n\n![Edge Cases](https://mistral.ai/_astro/6ba9925a-0be9-410e-ba7f-e23d961531c2_QUOiW.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\nThis straightforward mapping makes it easy to locate the tests for any given file, or to identify files that lack tests entirely.\n\nWhere it gets harder for our agent is that to avoid duplicating code, RSpec relies heavily on shared context: factories, fixtures, database schemas...\n\n- **Factories:** Reusable templates for creating test objects with predefined attributes, making it easy to generate consistent test data.\n\n- **Fixtures:** Static data files that preload test database records, providing a fixed baseline for tests.\n\nIf a factory file doesn’t exist, the agent creates it; if it does, the agent reuses it. Because factories are shared across many tests (unlike spec files), careless changes can easily break tests elsewhere, so updates to these files must be made with caution.\n\n## Building the agent with Vibe\n\nWe built the agent on top of [**Vibe**](https://mistral.ai/products/vibe), Mistral's open-source coding assistant. The default system prompt was sufficient for this project, so we focused on three levers: repository-level context, specialized skills, and custom tools.\n\n### Context engineering\n\nContext engineering was central to our approach. Vibe supports a repository-level `AGENTS.md` file: when running on a repository with this file at its root, its contents are automatically appended to the system prompt.\n\nThe `AGENTS.md` we used provided basic details about the target repositories, but mostly, it provided the agent with a **step-by-step execution plan:**\n\n```text\n1. Read the source file2. Read the documentation (if it exists)3. Check if a spec already exists4. Choose and read exactly one skill based on the source file location5. Find existing patterns, factories, and helpers6. Execute the skill (Extract → Factory → Generate tests)7. Validate with Rubocop tool8. Validate with SimpleCov tool\n```\n\nEach step included details about what to do and what the success criteria are. We also included some best practices of RSpec on areas where we felt it was important to orient the agent. Example:\n\n```text\n- **NEVER** use `be_present`, `be_truthy`, `be_between`, or `include(:key)`These are vague. Use `eq(exact_value)` always\n```\n\nWe found the agent would sometimes skip methods or leave edge cases untested: it would generate a spec that *looked* complete but quietly ignored a few public methods from the source file. To counter this, the `AGENTS.md` ends with a forced self-review: the agent must re-read the source file and explicitly ask itself \"Did I test every public method? Count them.\" before finishing. If anything is missing, it goes back.\n\nWith this generic `AGENTS.md` file forcing the agent to follow strict planning, our quality score went from **0.68 to 0.74**, all from a single markdown file with framework-level instructions.\n\n### Using SKILLS files:\n\nRecall step 4 of our `AGENTS.md`:\n\n`4. Choose and read exactly one skill based on the source file location`\n\nA single generic skill would produce mediocre results: the instructions precise enough for testing a model file are the wrong instructions for a controller file.\n\nWhat worked was creating a separate skills file for each category, plus one for plain Ruby files.\n\nHere is an example of a basic skills file for testing controllers:\n\n```text\n---name: \"Generate Request Spec\"description: \"Generate RSpec request tests for a Rails controller. Use when the source file is in app/controllers/.\"---\n# Generate Request Spec\n## File Scope\n- `spec/requests//_spec.rb` — drop `_controller` from the filename- `spec/factories/.rb` — create or update if needed\n## Example tests for Controllers\n# frozen_string_literal: truerequire 'rails_helper'\ndescribe 'Admin::Users', type: :request do  let(:user) { create(:user, :admin) }  before { sign_in user }\n  # Unauthorized access — one test per action  describe '#authorized?' do    let(:user) { create(:user) }    it 'GET /admin/users redirects' do      get '/admin/users'      expect(response).to have_http_status(:redirect)    end  end\n  # Each action: happy + sad paths  describe 'POST /admin/users' do    let(:valid_params)   { { user: attributes_for(:user) } }    let(:invalid_params) { { user: { email: '' } } }\n    context 'with valid params' do      it 'creates a record' do        expect { post '/admin/users', params: valid_params }.to change(User, :count).by(1)        expect(response).to have_http_status(:created)      end    end\n    context 'with invalid params' do      it 'returns unprocessable entity with errors' do        post '/admin/users', params: invalid_params        expect(response).to have_http_status(:unprocessable_entity)        json = JSON.parse(response.body, symbolize_names: true)        expect(json[:errors]).to include(\"Email can't be blank\")      end    end  endend\n## Critical Rules\n- **Assert content, not just status:** always parse JSON and verify exact values- **Test exact error messages:** `include(\"Email can't be blank\")`, not `be_present`- **Verify state changes:** use `.by(1)` and check the created record's attributes- **Cover all actions:** index, show, create, update, destroy, and any custom actions- **Test auth on every action:** verify unauthorized users get the correct status code- Use `let` only, never `@instance_variables`; `sign_in user` again before `follow_redirect!`\n```\n\n### Adding custom tools\n\nVibe has built-in tools for reading and writing files, running bash commands, and editing code. But it also supports custom tools & MCPs to expand its action space. For this project, custom tools were key.\n\nAfter some experimentation, we went for:\n\n- A [RuboCop](https://rubocop.org/) linting tool that runs the `rubocop` command to detect style violations, so that the agent goes back to the code and fixes them.\n\n- A [SimpleCov](https://github.com/simplecov-ruby/simplecov) tool integrated with RSpec that checks both code coverage and test correctness. Placing this as the final step was key: it guarantees every test the agent writes actually runs.\n\nBelow is a stripped-down version of how we implemented the Rubocop linting tool:\n\n```python\nimport asynciofrom pathlib import Path\nfrom pydantic import BaseModelfrom vibe.core.tools.base import BaseTool, ToolError\nclass RubocopLintResult(BaseModel):    command: str    output: str    success: bool    exit_code: int | None = None\nclass RubocopLint(BaseTool):    description = \"Lint spec files using Rubocop.\"\n    async def run(self, spec_path: str):        spec_path = Path(spec_path)\n        cmd = [\"rubocop\", \"--format\", \"simple\", str(spec_path)]        proc = await asyncio.create_subprocess_exec(            *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE        )\n        try:            stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=60)        except TimeoutError:            proc.kill()            await proc.wait()            raise ToolError(\"Rubocop timed out after 60s\")\n        output = stdout.decode(\"utf-8\", errors=\"ignore\")\n        yield RubocopLintResult(            command=\" \".join(cmd),            output=output,            success=proc.returncode == 0,            exit_code=proc.returncode        )\n```\n\nThe SimpleCov tool follows the same pattern but does more: it runs the spec file with RSpec, collects pass/fail results for each test, and reports the coverage percentage of the source file. The agent gets back which tests failed (with error messages), and which lines of the source file were never exercised. This gives it enough to self-correct when needed.\n\nWhen we first wired this tool in, only around a third of generated tests passed on first execution. The agent self-corrected all failures within a few iterations. Without this step, some of those tests would not have ran.\n\n## Measuring test quality\n\nA good unit test follows the [**Arrange-Act-Assert**](https://semaphore.io/blog/aaa-pattern-test-automation) pattern: set up state, execute one behavior, verify the outcome. It should use precise assertions, cover both happy and error paths, and produce failures that clearly identify what broke.\n\n![Good Testing](https://mistral.ai/_astro/32542f94-4778-4112-820f-203e96ef0735_Z1IC6ad.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\n### Tool-based metrics\n\nExisting Ruby on Rails tools allow us to get several metrics easily:\n\n- **RSpec:** the most popular testing framework for Ruby. Expressive syntax for writing readable, well-structured tests.\n\n- **RuboCop:** a static Ruby code analyzer and formatter, helping enforce style guidelines and keeping code consistent\n\n- **SimpleCov:** a code coverage tool for Ruby, measuring how much code is executed by the test suite.\n\nFrom these tools, we can extract several useful quantitative metrics: the proportion of tests that pass, the number of RuboCop violations per file, the proportion of code covered by the unit test suite, and the speed of execution. These metrics provide a solid baseline for measuring the overall performance of a test suite.\n\nBut these numbers alone cannot determine whether a test file is actually good. How should we compare two files that both have zero RuboCop violations, 100% coverage, and all tests passing? The quantitative metrics may look identical, yet qualitative differences in test quality can still exist.\n\n### LLM-as-a-judge\n\nThe idea behind LLM-as-a-judge is to ask an LLM: “Are all error conditions tested?” or “Is this a good test?” and get back a score from 0 to 1 based on clear criteria.\n\nExample prompt:\n\n```text\n## Good Tests\nPrecise values: `eq(100)`, not `be_present` or `be_a(Float)`.State changes: `change { User.count }.by(1)`.Error paths: `raise_error(ArgumentError, \"message\")`.Boundary cases: Test exact values at thresholds (e.g., `page(0)`).Public interface: No `.send(:private_method)`.\n## Bad Tests\nVague assertions: `be_truthy`, `be_an(Array)`.\"Doesn't crash\" tests: `not_to raise_error`.Testing private methods.Over-mocking (tests mocks, not behavior).No assertions on return values.\n## Scoring Rules\n- 1.0: Precise assertions on return values/state + all key paths covered (happy, error, boundaries).- 0.8: Precise assertions on all tested paths, but 1-2 edge cases missing entirely.- 0.6: All key paths covered, but 1-2 tests use vague assertions (e.g., `be_truthy` instead of `eq(expected)`) or minor over-mocking.- 0.4: Some tests have precise assertions; others are vague, over-mocked, or skip return value checks.- 0.2: Most tests lack meaningful assertions: vague matchers, stubbed-out logic, no verification of actual output.- 0.0: Tests are structurally broken: testing private methods, no real assertions, or verifying mocks instead of behavior.\n```\n\nThe \"Scoring Rules\" section here is especially important as it enforces the score to be more consistent across runs. Without it, the agent would lack clear definition of how strict or how lax the scoring is.\n\nSuch a score is less authoritative than a tool's output or a thorough developer review. But it is a good indication of how well the current version of our agent is doing, especially when run at scale across every file in a repository.\n\n#### Limitations of LLM-as-a-judge evaluation:\n\nAnything that can be put into text can be scored with LLM-as-a-judge, which makes it versatile. But the score output is not deterministic. Even with *temperature=0*, there will be variance due to the order of kernel operations. In practice the outputs for the same evaluation are always close, but this lack of determinism can be an issue for some use cases.\n\nFor our use case, since we score not a single test but usually tens to hundreds at a time, this was an acceptable limitation. The aggregated score stayed consistent.\n\n### The missing parenthesis problem\n\nThere was another core problem with LLM-as-a-judge: a test can be of great quality but not run, or have the logic behind it be wrong altogether.\n\nExample:\n\n```text\nRSpec.describe User, \"#full_name\" do  it \"combines first and last name with a space\" do    user = User.new(first_name: \"Ada\", last_name: \"Lovelace\"\n    expect(user.full_name).to eq(\"Ada Lovelace\")  endend\n```\n\nThis test looks great. It tests one behavior, has a clear description that reads as documentation, follows [**Arrange-Act-Assert**](https://semaphore.io/blog/aaa-pattern-test-automation):, and would fail with a useful message if broken.\n\nWe ran this test through multiple models using the scoring prompt defined earlier. The average score given was 0.75. A developer taking a quick look would likely agree.\n\nBut this test has one core flaw: **it will not run.** All because of a missing closing parenthesis `)` on the `User.new()`. This is a critical flaw that makes this test definitely not suitable.\n\nThis example is easy to mitigate. But there are a lot other possibilities that can lead to syntax error:\n\n- **Versioning errors:** Using methods or syntax from an incompatible Ruby or Rails version\n\n- **Non-existent methods:** some methods might have been removed, renamed, or never existed.\n\n- **Factory/fixture errors:** Referencing missing factories, incorrect attributes, or missing associations\n\n- **Missing dependencies:** Using gems or modules not included in the test environment\n\n## The experiment\n\nWe ran an experiment to check the agent could hold up against a real codebase. The target repository contained 275 source files. Half of them already had test coverage. The other half had none.\n\nWe pointed the agent at every file, whether it already had tests or not. For uncovered files, it generated specs from scratch successfully. For files that already had tests, it rewrote and improved them.\n\nWe scored every spec file before and after with LLM-as-a-judge. The aggregate score for tested files went from 0.49 to 0.74, and coverage reached 100%. There's still room for improvement, but the results validated the approach.\n\n| Metric                                   | Result |\n| ---------------------------------------- | ------ |\n| Files processed                          | 275    |\n| Tests passing                            | 100%   |\n| Average line coverage (SimpleCov)        | 100%   |\n| RuboCop violations after self-correction | 0      |\n| LLM-as-a-judge score                     | 0.74   |\n\n### By file type\n\nNot all file types were easy to test. Models scored highest: their business logic is self-contained and the test patterns are predictable. Controllers were harder, HTTP request handling introduced more room for error.\n\n| File type   | LLM-as-a-judge |\n| ----------- | -------------- |\n| Models      | 0.81           |\n| Controllers | 0.67           |\n| Serializers | 0.80           |\n\n### Does it run?\n\nBundling SimpleCov with RSpec execution as the final step, forcing the agent to run every test it wrote, was the single most impactful decision. Only a third of tests passed on first execution. The agent self-corrected all failures within a few iterations.\n\nWithout this step, LLM-as-a-judge would have rated these non-running tests as high quality: the missing parenthesis problem at scale.\n\n[**Vibe is open source**](https://github.com/mistralai/mistral-vibe). The agent, tools, and skills described here all run on top of it. Try it now.\n",
  "dateCreated": "2026-10-19T01:26:53.998049939+00:00",
  "date_created": "2026-03-11T12:00:00+00:00",
  "date_modified": "2026-03-11T12:00:00+00:00",
  "date_published": "2026-03-11T12:00:00+00:00",
  "description": "In most large Rails monoliths, organizations prioritize writing new features over writing tests for them. Over time, more and more code goes untested, forcing teams to spend more time debugging painful bugs. We built an autonomous agent…",
  "external_url": "https://mistral.ai/news/rails-testing-on-autopilot-building-an-agent-that-writes-what-developers-wont/",
  "git": {
    "commit": "ebb0941c5c06a2ab2377342d607535262a3e969e",
    "path": "items/mistral-ai/2026/03/2026-03-11-rails-testing-on-autopilot-building-an-agent-that-writes.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/ebb0941c5c06a2ab2377342d607535262a3e969e/items/mistral-ai/2026/03/2026-03-11-rails-testing-on-autopilot-building-an-agent-that-writes.md"
  },
  "id": "urn:aggr:item:2b0d0bc6a60e0d57e2cb79c19af4f63da35708e8",
  "isBasedOn": {
    "@id": "https://mistral.ai/news/rails-testing-on-autopilot-building-an-agent-that-writes-what-developers-wont/"
  },
  "mainEntity": {
    "@id": "https://mistral.ai/news/rails-testing-on-autopilot-building-an-agent-that-writes-what-developers-wont/",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/mistral-ai/2026-03-11-rails-testing-on-autopilot-building-an-agent-that-writes/#webpage"
    },
    "dateModified": "2026-03-11T12:00:00+00:00",
    "datePublished": "2026-03-11T12:00:00+00:00",
    "headline": "Rails testing on autopilot: Building an agent that writes what developers won't",
    "timeRequired": "PT11M",
    "url": "https://mistral.ai/news/rails-testing-on-autopilot-building-an-agent-that-writes-what-developers-wont/",
    "wordCount": 2381
  },
  "name": "Archived snapshot: Rails testing on autopilot: Building an agent that writes what developers won't",
  "preview": null,
  "reading_minutes": 11,
  "replicated_at": null,
  "source": "mistral-ai",
  "source_name": "Mistral Blog",
  "summary": null,
  "tags": [],
  "temporalCoverage": "2026-10-19T01:26:53.998049939+00:00",
  "title": "Rails testing on autopilot: Building an agent that writes what developers won't",
  "url": "https://aggr.aymericbeaumet.com/items/mistral-ai/2026-03-11-rails-testing-on-autopilot-building-an-agent-that-writes/",
  "word_count": 2381
}