Skip to content
Of Ash and Fire Logo

Multi-Language Code Quality Framework: TypeScript, Ruby & Elixir

How we established a unified code quality framework across TypeScript, Ruby, and Elixir codebases — combining static analysis, automated testing, and security scanning.

The Challenge: Maintaining Quality Across Three Language Ecosystems

Modern software organizations rarely operate within a single language ecosystem. At Of Ash and Fire, our engineering teams build and maintain production systems across TypeScript, Ruby, and Elixir — each chosen for its strengths in specific domains. TypeScript powers our frontend applications and Node.js services. Ruby on Rails drives rapid API development and content platforms. Elixir and Phoenix handle real-time systems, high-concurrency workloads, and fault-tolerant backends.

This polyglot approach delivers significant technical advantages, but it introduces a serious operational challenge: how do you enforce consistent code quality standards across three fundamentally different language paradigms? Each ecosystem has its own conventions, its own tooling philosophy, and its own failure modes. A linting rule that catches a critical pattern in TypeScript has no equivalent awareness of the metaprogramming pitfalls common in Ruby or the process-supervision concerns unique to Elixir.

The challenge intensified as AI-assisted code generation became part of our development workflow. While tools like GitHub Copilot and Claude accelerate development, they also introduce a new category of risk: code that compiles, passes superficial review, and even appears idiomatic — but harbors subtle inconsistencies, security gaps, or untested edge cases that a human developer familiar with the codebase would never introduce. We needed a unified code quality framework that could catch these issues before they reached production, regardless of which language ecosystem produced them.

Our Approach: Language-Specific Toolchains, Unified Quality Gates

Rather than forcing a one-size-fits-all solution onto three distinct ecosystems, we built a framework that respects each language's paradigm while enforcing a shared set of quality principles. Every codebase answers the same four questions before code can merge: Is it well-structured? Is it safe? Is it tested? Is it consistent? The tools that answer those questions differ by language, but the CI/CD pipeline enforces all of them uniformly.

TypeScript: Static Analysis and Strict Compiler Enforcement

TypeScript's type system is one of the most powerful static analysis tools available in any language — but only when configured aggressively. Many teams leave TypeScript in its permissive default mode, which allows any types, unchecked index access, and loose optional property handling. This defeats much of the purpose of using TypeScript in the first place, and it is precisely the kind of code that AI generators tend to produce.

Our TypeScript configuration enforces the strictest possible compiler settings:

  • strict: true — Enables all strict type-checking options as a baseline, including strictNullChecks, strictFunctionTypes, and strictBindCallApply
  • noUncheckedIndexedAccess — Forces developers to handle the possibility that an array or object index access returns undefined, eliminating an entire class of runtime errors that AI-generated code frequently introduces
  • exactOptionalPropertyTypes — Distinguishes between a property that is missing and one that is explicitly set to undefined, catching subtle data-handling bugs

On top of the compiler, we run ESLint with strict, customized rulesets that go well beyond the recommended defaults. Our configuration flags unused variables, enforces consistent import ordering, prohibits floating promises, and requires explicit return types on exported functions. Prettier handles formatting automatically, removing style debates from code review entirely.

For testing, we use Vitest — a modern, fast test runner that integrates seamlessly with our TypeScript toolchain. Every module that contains business logic requires accompanying unit tests, and our CI pipeline reports coverage metrics to prevent regression.

AI-generated TypeScript code frequently uses any types as escape hatches, omits null checks on indexed access, and produces functions with implicit return types. Our strict compiler configuration catches these issues at build time — before they ever reach code review.

Ruby/Rails: Convention Enforcement and Security Scanning

Ruby's expressiveness and metaprogramming capabilities make it a powerful language for rapid development, but they also make it uniquely susceptible to inconsistent patterns, especially when AI tools generate code that technically works but violates established project conventions.

Our Ruby quality framework centers on three pillars:

  • RuboCop — We run RuboCop with department-specific configurations covering Style, Lint, Metrics, and Rails cops. Custom rules enforce our project's naming conventions, method length limits, and class complexity thresholds. RuboCop catches the kind of style drift that AI-generated code introduces when it produces working but inconsistent patterns — a method that uses each where the rest of the codebase uses map, or a controller action that handles errors differently from every other controller in the project.
  • Brakeman — This static analysis security scanner is purpose-built for Rails applications. It detects SQL injection vulnerabilities, cross-site scripting (XSS) vectors, mass assignment risks, unsafe redirects, and dozens of other Rails-specific security issues. Brakeman is particularly valuable for catching AI-generated code that uses raw SQL queries or fails to sanitize user input — patterns that appear functional in development but create serious vulnerabilities in production.
  • RSpec with FactoryBot and Shoulda Matchers — Our test suite uses RSpec as the test framework, FactoryBot for test data generation, and Shoulda Matchers for concise model and controller validation testing. SimpleCov tracks code coverage and fails the build if coverage drops below our established threshold.

The combination of RuboCop and Brakeman is especially effective at catching AI-generated code issues. An AI model might produce a perfectly functional ActiveRecord query that bypasses the project's established query object pattern, or generate a controller that handles authentication in a way that works but does not match the application's security architecture. Our toolchain catches both the style violation and the security implication.

Elixir/Phoenix: Functional Purity and Concurrency Safety

Elixir operates in a fundamentally different paradigm from TypeScript or Ruby. Its functional nature, immutable data structures, and actor-model concurrency (via the BEAM virtual machine) require quality tools that understand these concepts natively. Generic code quality approaches simply do not apply.

Our Elixir quality framework includes:

  • Credo — Elixir's primary static analysis tool focuses on code consistency, readability, and adherence to community conventions. Credo catches refactoring opportunities, identifies overly complex functions, and flags code that deviates from Elixir idioms. When AI generates Elixir code, it often produces imperative-style logic wrapped in functional syntax — Credo identifies these patterns and suggests idiomatic alternatives.
  • Sobelow — A security-focused static analysis tool built specifically for Phoenix applications. Sobelow checks for insecure configurations, hardcoded secrets, SQL injection in Ecto queries, XSS vulnerabilities in templates, and unsafe deserialization. It understands Phoenix's specific security model, including its plug pipeline and CSRF protection mechanisms.
  • Dialyxir — Dialyzer, accessed through the Dialyxir mix task, performs success typing analysis on Elixir code. While Elixir is dynamically typed, Dialyzer uses type inference and typespecs to catch type errors, unreachable code, and contract violations without requiring explicit type annotations. This is critical for catching AI-generated code that passes the wrong data types between functions — errors that would only surface at runtime without Dialyzer's analysis.
  • mix format — Elixir's built-in formatter enforces consistent code style across the entire codebase, similar to Prettier for TypeScript. It is non-negotiable and runs as a check in CI.
  • ExUnit with ExMachina and Mox — ExUnit is Elixir's built-in test framework. We pair it with ExMachina for test data factories (analogous to FactoryBot in Ruby) and Mox for behavior-based mocking that enforces explicit contracts between modules. Mox is particularly valuable because it requires mocked modules to implement a defined behaviour (Elixir's equivalent of an interface), preventing tests from masking integration failures.

AI-generated Elixir code often mimics object-oriented patterns — storing state in process dictionaries, using excessive if/else chains instead of pattern matching, or ignoring OTP conventions for process supervision. Credo and Dialyxir catch these anti-patterns systematically.

The CI/CD Pipeline: Where Quality Becomes Non-Negotiable

Tools are only effective if they are enforced. Every one of the checks described above runs as a mandatory gate in our CI/CD pipeline. Code cannot merge to the main branch without passing all of them. There are no overrides, no "fix it later" exceptions, and no manual approval bypasses.

The pipeline for each language runs in parallel stages:

  • Stage 1: Formatting — Prettier (TypeScript), RuboCop style cops (Ruby), mix format --check-formatted (Elixir)
  • Stage 2: Static Analysis — ESLint + TypeScript compiler (TypeScript), RuboCop lint/metrics cops (Ruby), Credo + Dialyxir (Elixir)
  • Stage 3: Security Scanning — npm audit (TypeScript), Brakeman (Ruby), Sobelow (Elixir)
  • Stage 4: Test Suite — Vitest (TypeScript), RSpec with SimpleCov (Ruby), ExUnit (Elixir)
  • Stage 5: Coverage Enforcement — Coverage thresholds verified across all three ecosystems

If any stage fails in any language, the entire pull request is blocked. This creates a feedback loop where developers — and the AI tools assisting them — learn to produce code that meets the standard on the first attempt.

Catching AI-Generated Code Issues

The rise of AI-assisted development made this framework not just useful, but essential. Through months of production use, we have identified the most common patterns where AI-generated code fails our quality gates, as documented in our analysis of the AI-generated code quality crisis:

  • Type safety shortcuts — AI models frequently use any in TypeScript, skip typespec annotations in Elixir, or use overly permissive method signatures in Ruby. Our strict compiler settings and Dialyxir checks catch these immediately.
  • Inconsistent patterns — AI generates code that works but does not match the existing codebase's architecture. A new service class that handles errors differently, a controller that bypasses the established authentication middleware, or a GenServer that does not follow the project's supervision tree conventions. RuboCop, Credo, and ESLint custom rules catch pattern drift.
  • Security vulnerabilities — AI-generated code sometimes introduces raw SQL queries, unsanitized user input handling, or insecure default configurations. Brakeman and Sobelow are specifically designed to catch these Rails and Phoenix security issues.
  • Insufficient test coverage — AI often generates implementation code without corresponding tests, or produces tests that only cover the happy path. Our coverage enforcement and testing requirements for AI-generated code ensure that edge cases, error handling, and boundary conditions are all verified.
  • Idiomatic violations — Code that works but fights the language. Imperative loops in Elixir instead of pattern matching and recursion. Procedural Ruby instead of object-oriented design. Callback-heavy TypeScript instead of async/await. Static analysis tools tuned to each language's idioms catch these anti-patterns.

Results

After implementing and refining this multi-language code quality framework over twelve months, we measured its impact across all three codebases:

  • 67% reduction in production bugs — Defects that reached production dropped by two-thirds compared to the twelve months prior to framework adoption. The most significant improvements came from TypeScript strict mode (eliminating null/undefined runtime errors) and Brakeman/Sobelow security scanning (preventing vulnerabilities before deployment).
  • 41% reduction in code review cycle time — With formatting, style, and basic correctness issues caught automatically, human code reviewers could focus on architecture, business logic, and design decisions. Pull requests that previously required two or three review rounds for style fixes now passed on the first substantive review.
  • Zero critical security vulnerabilities in production — Across all three language ecosystems, no critical or high-severity security vulnerabilities reached production after the framework was fully implemented. Brakeman and Sobelow together caught 23 potential vulnerabilities during the measurement period that would have otherwise required post-deployment patches.
  • 34% faster onboarding for new developers — New team members (and AI coding assistants) received immediate, automated feedback on project conventions. Instead of learning patterns through trial-and-error code review, the toolchain taught them the codebase's standards with every commit.
  • 89% AI-generated code acceptance rate — After initial calibration, AI-generated code that passed through our quality gates required minimal additional human modification. The framework effectively filtered out low-quality AI output while allowing high-quality contributions to flow through efficiently.

Key Takeaways

Building a multi-language code quality framework is not about finding a universal tool — it is about establishing universal principles and implementing them with language-appropriate instrumentation. The four questions remain constant across every ecosystem: Is it well-structured? Is it safe? Is it tested? Is it consistent? The tools change; the standards do not.

This approach is especially critical in the age of AI-assisted development. AI code generators are powerful accelerators, but they require guardrails. A well-configured quality framework transforms AI from a liability into a force multiplier — the AI handles the volume, and the toolchain ensures the quality.

If your engineering organization is navigating the challenges of multi-language codebases, AI-generated code quality, or inconsistent development practices, we can help you build a framework tailored to your specific technology stack and quality requirements. Contact our team to discuss how we can establish quality gates that protect your production systems without slowing down your development velocity.

Project Highlights

1. Language-Specific Toolchains

Calibrated static analysis for TypeScript, Ruby, and Elixir — each tuned to catch the issues AI code generators commonly introduce.

2. Unified Quality Gates

Every PR must pass formatting, static analysis, security scanning, and test coverage across all three ecosystems before merge.

3. Measurable Results

67% fewer production bugs, 41% faster code review cycles, zero critical security vulnerabilities in production.

Key Features

TypeScript strict mode + ESLint

Ruby/Rails RuboCop + Brakeman

Elixir Credo + Sobelow + Dialyxir

Unified CI/CD quality gates

AI-generated code detection

67% reduction in production bugs

Get In Touch

For Fast Service, Email Us:

info@ofashandfire.com

Our Approach

Discovery & Planning

We begin each project with a thorough understanding of client needs and careful planning of the solution architecture.

Implementation

Our experienced team executes the solution using modern technologies and best practices in software development.

Results & Impact

We measure success through tangible outcomes and the positive impact our solutions have on our clients' businesses.

Ready to Ignite Your Digital Transformation?

Let's collaborate to create innovative software solutions that propel your business forward in the digital age.