Rubyzen

Rubyzen is an architectural linter for Ruby that lets you write architectural lint rules as unit tests, inspired by Konsist (for Kotlin) and Harmonize (for Swift).

Architectural linters in the era of AI-generated code

In the era of AI-generated code, architectural flaws and subtle bugs happen faster than ever. AI agents produce code that passes tests and looks reasonable but subtly violates your team's architecture. As more code is produced, faster, it becomes impractical for manual code reviews to catch all these violations.

Architectural lint rules act as deterministic guardrails. They catch the architectural or structural mistakes that AI introduces, such as calling the database from a presenter or performing business logic in a controller, before they get merged. And since they run as unit tests, they provide immediate feedback to the AI agents to fix their own code.

Why Yet Another Linter?

Traditional linters such as RuboCop require dealing with the raw AST, which has a steep learning curve and makes rules hard to write, read, and maintain. Rubyzen abstracts away the AST details, providing a high-level API that allows developers to write lint rules in a more natural way — the same way we write tests.

Advantages

  • Readable, Easy-to-Use API: Rubyzen provides a high-level API to access files, classes, methods, parameters, and more, without having to deal with low-level AST operations.

  • Architectural Enforcement & Documentation: By writing lint rules as tests, you can use the Given-When-Then style to enforce and document your architecture directly within the codebase.

  • Less Manual Reviews: With architectural rules automatically enforced, code reviews can focus on complex issues instead of repeating the same architectural feedback.

  • AI-Friendly Feedback Loop: When lint rules fail, the failure messages tell AI agents exactly what they violated and where, allowing them to self-correct their code.

Setup

Add Rubyzen to your Gemfile:

gem 'rubyzen-lint', group: :test

Then run bundle install.

That's it. Rubyzen auto-discovers your project structure (app/, lib/, src/, spec/) from your project root. No environment variables or configuration needed.

Write your first set of lint rules

Create a spec file anywhere in your project (e.g., spec/architecture/sample_spec.rb) and start enforcing your architecture:

require 'rubyzen'

RSpec.describe 'Architecture rules' do
  let(:project) { Rubyzen::Project.new }
  let(:controllers) { project.files.with_paths('app/controllers/').classes }
  let(:presenters) { project.files.with_paths('app/presenters/').classes }

  it 'controllers do not call ActiveRecord directly' do
    expect(controllers.all_methods.call_sites.with_name('where')).to zen_empty
  end

  it 'controllers inherit from ApplicationController' do
    expect(controllers).to zen_true { |c| c.superclass_name == 'ApplicationController' }
  end

  it 'presenters do not depend on repositories' do
    expect(presenters.all_methods.call_sites).to zen_false { |cs|
      cs.receiver&.end_with?('Repository')
    }
  end
end

You can find more sample lint rules in the sample_project/spec/ directory.

Run your lint rules

bundle exec rspec spec/architecture/

Custom Paths (Optional)

By default, Rubyzen::Project.new scans standard directories (app/, lib/, src/, spec/) from your project root. You can override this:

# In your spec file — scope to specific directories
project = Rubyzen::Project.new(['app/models', 'app/controllers'])

# Or in spec/spec_helper.rb — configure globally for all specs
Rubyzen.configure do |config|
  config.paths = ['app', 'lib']
end

CI Integration

Add a step to your existing CI workflow to run your lint rules automatically when a PR is opened:

- name: Run architecture lint rules
  run: bundle exec rspec spec/architecture/

AI Agent Skills

Rubyzen includes agent skills in .claude/skills/ (also symlinked at .github/skills/) that work with both Claude Code and GitHub Copilot:

Skill Purpose
write-lint-rule Write an architectural lint rule using the Rubyzen API
run-lint-rules Run sample project lint rules and verify the violations are detected
expand-rubyzen Add a new Rubyzen API (Declaration + Provider + Collection)
add-rubyzen-tests Write unit tests for Rubyzen's own components
run-tests Run Rubyzen's unit test suite

Contributing

Contributions are welcome! See CONTRIBUTING.md for instructions on setting up the project, enhancing the API, and adding tests.