Compatibility

What's actually supported, what's actually proven, and where each claim comes from.

Architecture · Introspectors · Troubleshooting · FAQ


Every claim below cites the spec file or release that proves it. No entry in this document is aspirational.

Supported versions

The gemspec's railties bound is wider than the CI matrix: point releases inside 7.0-8.1 and any future 8.x minor satisfy Bundler's constraint without a gem release, but only the combinations in the table below are actually exercised by CI.

CI matrix

Source: .github/workflows/unit-matrix.yml, which both ci.yml and release.yml call. 17 of 25 possible Ruby x Rails combinations run (fail-fast: false, so one failing cell doesn't hide the rest):

Ruby \ Rails 7.0 7.1 7.2 8.0 8.1
3.1 yes yes yes - -
3.2 - yes yes yes yes
3.3 - yes yes yes yes
3.4 - yes yes yes yes
4.0 - - - yes yes

Operating systems

Linux, macOS and Windows. CI runs the unit suite on Linux for every cell above, and on macOS (Ruby 3.4 / Rails 8.1 and Ruby 3.1 / Rails 7.0) and Windows (Ruby 3.4 / Rails 8.1). The E2E harness, which builds real apps and drives the CLI, the rake tasks and both transports, runs on Linux for six Ruby / Rails pairs and on macOS and Windows for Ruby 3.4 / Rails 8.1. A release waits for all of them.

On Windows:

Rails 9

Not supported, and the two install paths fail differently:

Operating tiers

Two tiers, both reachable over the CLI and MCP (stdio and HTTP):

RUNTIME - the app booted. Introspectors read live Rails reflection (ActiveRecord connections, Rails.application.routes, loaded classes) plus the Prism AST layer for source-level facts (scopes, callbacks, strong params).

STATIC - the app didn't boot, or --no-boot was passed. 32 of the 40 introspectors answer here; each declares which of three kinds it is (ADR-0002), so what a tier can say is a declaration rather than a guess.

files-only (22) run unchanged against the static app handle, because they only ever read files: gems, views, view_templates, turbo, stimulus, active_storage, action_text, auth, tests, rake_tasks, assets, devops, action_mailbox, migrations, seeds, env_config, multi_database, components, performance, frontend_frameworks, credentials and env.

alternate-source (10) have a static_call that reads a different source from the booted path:

Introspector Static source
schema db/schema.rb / db/structure.sql / migration replay over every db/migrate the repo owns, following the files a migration requires under db/
models app/models/**/*.rb (plus packs, in-repo engines/plugins/modules, extra paths) parsed, not constantized
routes config/routes.rb parsed with a dedicated Prism listener
controllers app/controllers/**/*.rb (plus packs, in-repo engines/plugins/modules, extra paths) parsed, not constantized
jobs app/jobs, app/workers, app/sidekiq, app/mailers and app/channels parsed for classes and their public methods, plus any class elsewhere under app/ whose parent chain reaches ActionMailer::Base
i18n config.i18n.available_locales read from config/, or every top-level key across config/locales when the app never assigns it; the default locale read from config/. The backend and the fallbacks stay in the answer and are declared unanswered, being facts about the running process
api every detection but the mode is a file read and runs unchanged; config.api_only comes from the assignment in config/application.rb
engines the mounts in config/routes.rb and the files it draws, plus the in-repo engines found in the tree; the loaded Rails::Engine classes are declared unavailable
active_support concerns, message verifier and encryptor use, notification subscriptions and a TaggedLogging initializer read from source; deprecators, load hooks and the cache store are declared unavailable
middleware app/middleware, lib/middleware, and the stack changes in config/initializers, config/environments and config/application.rb; the booted stack and its count are declared unavailable

runtime-only (8) report { unavailable: reason } here, and only these: conventions, database_stats, config, initializers, autoload, connection_pool, security and observability. The fallback message comes from Introspector#run_introspector (lib/rails_ai_context/introspector.rb) and is the same whatever put the gem in the static tier.

doctor never enters the static tier - its job is diagnosing why boot failed, so it always requires a bootable app.

Boot failure degrades serve/tool to the static tier automatically (proven across five boot-failure modes - raises, prints to stdout, writes via the STDOUT constant, calls exit/abort, and hangs past the timeout - in spec/e2e/boot_resilience_spec.rb); --no-boot forces it without attempting a boot at all (spec/e2e/static_tier_spec.rb).

The exit mode is the standalone binary only. The rake tasks boot inside the app's own process, where exit is that process deciding to stop, so rails ai:serve on an app that aborts stops with it.

Confidence vocabulary

Source: lib/rails_ai_context/confidence.rb.

Tag Meaning
[VERIFIED] Runtime-confirmed value, or a static literal the AST resolves with certainty
[STATIC] Derived from source files without a booted app - trustworthy structure, not runtime-confirmed
[INFERRED] Heuristic: a dynamic expression, metaprogramming, or a runtime-only construct the AST can't fully resolve
[UNAVAILABLE: reason] The data source is absent entirely

[VERIFIED]/[INFERRED] are per-value tags applied inside the schema, model, route, controller, and mailbox-routing introspectors as they walk the AST (Confidence.for_node). [UNAVAILABLE] is a whole-response tag, marking a section with no static path, or (in either tier) a data source that genuinely doesn't exist for this app. It also marks a single row: a model or controller whose file could not be read is named with the reason, in the generated files and the tool listings alike, rather than rendered as an entry that declares nothing. The count above the row still includes it, because the app has it.

[STATIC] marks the whole response of any answer that came from the static tier, and it also caps the records inside it: no record can claim more than the tier carrying it, so a static model entry's associations, validations, scopes and methods read [STATIC] rather than the [VERIFIED] the AST walk would give them on its own. A record the parser could not resolve keeps its lower [INFERRED]. The same cap decides the header of a hydrated Schema Hints block.

A generated context file says the same thing. A run that did not boot writes a [STATIC] line under the header of every file that states app counts (CLAUDE.md, AGENTS.md, the Copilot instructions, the split AGENTS.md pair and every per-tool rules file except the MCP tool reference, which lists the gem's own tools in either tier), and .ai-context.json carries the tier as a tier key reading static or booted. A booted run writes no notice into the markdown files, so a file that states app counts and carries none came from a booted run; .ai-context.json names its tier either way.

A model's file in .ai-context.json is app-relative, except for a model a gem owns: that value begins gem: and the rest of it starts with the gem's own directory name, as in gem:doorkeeper-5.9.5/app/models/access_grant.rb. It is relative to the directory the gems are unpacked under, not to the app root, so a consumer must not join it to the app root. The gem's own readers ask PortablePath.resolve, which answers both shapes.

Shape matrix

Rows are app shapes; columns are schema, models, routes, and controllers (the four whose static behavior varies by shape), plus views. n/a means the shape doesn't change that introspector's behavior - see the stock full-stack row for its baseline proof. Reference numbers point at the proof list below the table.

Shape Schema Models Routes Controllers Views
Stock full-stack (schema.rb) runtime + static [1] runtime + static [1] runtime + static [1] runtime + static [1] runtime [2] + static [8]
API-only runtime + static [1] runtime + static [1] runtime + static [1] runtime + static [1] runtime, "not applicable" [3]
structure.sql - PostgreSQL dialect static [4] n/a n/a n/a n/a
structure.sql - MySQL/Trilogy dialect static [4] n/a n/a n/a n/a
structure.sql - SQLite dialect static [4] n/a n/a n/a n/a
Multi-DB solid_* schema dumps static, secondary_databases [5] n/a n/a n/a n/a
Packwerk packs/ n/a static [5] n/a n/a n/a
In-repo engines/ n/a n/a n/a static [5] n/a
In-repo plugins/, modules/, gems/plugins/ n/a static [5] n/a static [5] n/a
Mongoid [UNAVAILABLE], honest signal [6] static (fields + embeds) [6] n/a n/a n/a
Broken-boot (any full-stack app) static [7] static, per-file isolation [7] static [7] static [7] static [8]
Packs + engines under --no-boot n/a static [5] n/a static [5] n/a
Empty/greenfield app (no scaffold) graceful, no crash [9] graceful, no crash [9] graceful, no crash [9] graceful, no crash [9] graceful, no crash [9]
Massive app (500 models/tables) no crash at scale [10] no crash at scale [10] no crash at scale [10] n/a n/a

Proof sources:

  1. spec/e2e/in_gemfile_install_spec.rb, standalone_install_spec.rb, zero_config_install_spec.rb (schema, routes, model_details, and controllers exercised across the install-path suite - in-Gemfile, standalone, zero-config); real Rails 8.0 apps in the v5.14.0 release QA (blog, sandbox); a large open-source app (Rails 8.1, Ruby 3.4) in the v5.25.0 release QA, booted and static tiers, standalone and in-Gemfile installs; the same app again in the v5.26.0 release QA, with packs, in-repo engines, Postgres and concurrent tool calls covered by hand where no lab shape plants them; a private Rails 8.0.5.1 API-only app (Ruby 3.4.10, 133 models, 102 controllers, 1630 ActiveInteraction services, 516 Sidekiq workers) in the v5.27.0 QA round, booted and static tiers, with every report rebuilt on a minimal Rails 8.0.5.1 fixture before it was filed. In the v5.30.0 release QA: 12 open-source apps in the static and failed-boot tiers, and the same private app in every tier and both install paths. In the v5.30.1 release QA: the same private app on PostgreSQL 17 with a partitioned table added, booted and static from schema.rb and structure.sql, a Rails 7.2 app whose schema.rb carries no partition marker, and four of the open-source apps answering as before. In the v5.30.2 release QA: 14 real apps answered by v5.30.1 and by the release branch side by side, and the lab regressions on SQLite in all three tiers, PostgreSQL and MySQL. In the v5.31.0 release QA: a private Rails 8.0 API app booted and static, and eight of the open-source apps answered by v5.30.2 and by the release branch side by side, every changed answer checked against the app's source; the lab regressions on SQLite in all three tiers, PostgreSQL and MySQL; and the unit suite on Ruby 3.1 with Rails 7.0. In the v5.32.0 release QA: the same private app booted and static, and 13 open-source apps answered by v5.31.0 and by the release branch side by side, every changed answer checked against the app's source; 23 small apps built one per project shape (API-only, no Active Record, Mongoid, Sequel, multi-database, engine root and dummy, packs, monorepo, multi-tenant, GraphQL, view-heavy, Rails 6.1 and 7.0, Sorbet, Sinatra and a plain gem); the lab regressions on SQLite in all three tiers, PostgreSQL and MySQL; and the unit suite on Ruby 3.1 with Rails 7.0 through Ruby 4.0 with Rails 8.1. In the v5.32.1 release QA: a PostgreSQL 17 lab on Rails 8.1 and 8.0 with a two-schema search path, answered booted and static from schema.rb and structure.sql side by side; the small apps and the same private app answering as v5.32.0 did; and the unit suite on Ruby 3.1 with Rails 7.0 through Ruby 4.0 with Rails 8.1. In the v5.32.2 release QA: rails_query on the same private app's PostgreSQL database, with comment markers inside string literals, E strings and dollar quotes, a #>> that starts a line, and every spelling of a row limit; the installer's cleanup on a small Rails 8.1 app holding hand-written rule files; and the unit suite on Ruby 3.1 with Rails 7.0 through Ruby 4.0 with Rails 8.1. In the v5.33.0 release QA: the release gem in a fresh app on Ruby 3.1 with Rails 7.0 and 7.1, Ruby 3.2 with Rails 8.0, and Rails 8.1 on Ruby 3.3, 3.4 and 4.0, each through the standalone and in-Gemfile installs, the rake tasks and all 45 of that release's tools over MCP stdio; a Rails 8.1 app reached through a symlinked path, as macOS spells its temporary folders; a Rails 8.1 and a Rails 7.0 app mounting the engine, six clients calling while a model changed every 0.3 seconds; the pre-commit hook installed and run by real commits in an app's own repository, a monorepo's folder and a submodule; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.34.0 release QA: the 21 apps of the v5.33.0 acceptance run (12 open-source apps, a private app and the small blogs), 11 booted and 10 static, their answers compared with v5.33.0's, and their strong params and model tables compared again after the last fix; a fresh Rails 8.1 and a fresh Rails 7.0 app through the install, ai:context twice, zeitwerk:check with the engine mounted, MCP over the mounted HTTP endpoint and stdio, and the standalone CLI; an app that loads a gem before bundler/setup; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.34.1 release QA: the gem built from the release commit and its file list read, and the unit suite and the E2E harness on Linux, macOS and Windows; the code is v5.34.0's. In the v5.35.0 release QA: every copy-paste prompt in docs/PROMPTS.md pasted into Claude Code (claude -p) and Codex CLI (codex exec) and the result checked on disk, 40 scenarios in fresh Rails 8.1 apps, an app that cannot boot, a mountable engine, a Docker container and a copy of a private production app; each AI tool's MCP config started the way its client starts it; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.35.1 release QA: rails_query through its tool call on real PostgreSQL 16, MySQL 8.0 and 8.4 and MariaDB 10.6 and 11.4 servers, through mysql2 and Trilogy, on Rails 7.0, 8.0 and 8.1, now a CI job of its own; files of statements sent through the tool on each server and on SQLite before and after each fix, with every changed answer traced to its fix; the tool's own checks timed on Ruby 3.4 for statements up to a megabyte and on Ruby 3.1 for statements of 50 kilobytes; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.35.2 closing review: the statement files of the v5.35.1 QA sent through the tool again on PostgreSQL 16, MySQL 8.4, MariaDB 11.4 and SQLite, on the v5.35.1 code and on the fixed code, with every changed answer traced to its fix; and for each fix an example that fails on the v5.35.1 code, on a real server where the fix is a server's. In the v5.36.0 release QA: for each fix an example that fails on the v5.35.2 code, on a real server where the fix is a server's; rails_query through its tool call in CI on PostgreSQL 16, MySQL 8.0 and 8.4 and MariaDB 10.6 and 11.4 on Rails 7.0 (mysql2), 8.0 and 8.1 (mysql2 and Trilogy), and on PostgreSQL 13, 14, 15, 17 and 18 on Rails 8.0; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.36.1 release QA: every issue repro of 5.36.0 run again on the published code on PostgreSQL 13 to 18, MySQL 8.0 and 8.4 and MariaDB 10.6 and 11.4, the booted and static mailer lists compared on one app, and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.36.2 release QA: each fix's example failing on the v5.36.1 code; the issues' repros run through the CLI on a minimal app on the v5.36.1 code and on the fix; the static answers of 13 corpus apps compared between the two; MariaDB 10.6 and 11.4 through mysql2 built against MariaDB's client library on Linux; rails_runtime_info against a real Sidekiq and Redis; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.36.3 release QA: each fix's example failing on the v5.36.2 code; the issues' repros run through the CLI on a minimal app, and on copies of two large apps (both in the static tier, one also booted) against counts taken from their files; the introspector, tool and integration specs on Ruby 3.1 with Rails 7.0 and 7.2, Ruby 3.3 with Rails 7.1, Ruby 3.4 with Rails 8.0 and 8.1, and Ruby 4.0 with Rails 8.1; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.36.4 release QA: the update check run as the installed binary on Ruby 3.1, 3.3, 3.4 and 4.0 against rubygems.org, through a terminal and over MCP stdio on a fixture app, a large private app booted and another in the static tier, with the app's pinned json and net-http still loaded; the suite on prism 1.4.0 and on prism main under Ruby 3.2 and 3.4; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.36.5 release QA: each fix's example failing on the code before it; the advice rails_migration_advisor writes run as real migrations under strong_migrations 1.8 to 2.8 and Active Record 7.0 to 8.1 against PostgreSQL 13, 16 and 17, MySQL 8.0 and 8.4, MariaDB 10.6 and 11.4 and SQLite, for bigint, integer, unsigned, serial and uuid keys; its answers on three corpus apps in the static tier; the unit suite on Ruby 3.1 to 4.0 with Rails 7.0 to 8.1 on macOS, with seeds 1, 27377 and 90210 and defined order; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.36.6 release QA: each fix's example failing on the code before it; the models section (associations, validations, enums, scopes and concerns of every model) and the answers of rails_migration_advisor, rails_dependency_graph, rails_analyze_feature and rails_performance_check on eleven open-source and private apps, one of them on Mongoid, in the static tier, compared with v5.36.5 and every changed answer checked against the app's source; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.36.7 release QA: each fix's example failing on the code before it; the rails_query gate and its reads run against live PostgreSQL 17, MySQL 9.6 and MariaDB 13 for the dollar-quote and -- comment readings, with each server confirmed to read the text the way the gate does; the security, gate, redaction and database specs on Rails 7.0 (Ruby 3.1), 7.1, 7.2 and 8.1 against live PostgreSQL; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.36.8 release QA: each fix's example failing on the code before it; rails_read_logs against Active Record 8.0's own logs from live PostgreSQL 16, MySQL 8.4 and MariaDB 13 for inline values, binds and the errors that repeat a row, and against a JSON logger's lines; the static-tier settings reader, rails_search_code, rails_get_config and rails_review_changes on five open-source apps and two private ones with --no-boot, compared with v5.36.7 and every changed answer checked; the touched specs on Rails 7.0 (Ruby 3.1) and on prism 1.4; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.36.9 release QA: each fix's example failing on the code before it; doctor's plugin checks against what Claude Code 2.1.308 and 2.1.309 do with the same settings (marketplaces, enabled plugins, server command and URL rules, managed files and managed-mcp.json), and the plugin's launcher under Ruby 2.6 and 3.1; rails_read_logs and rails_query on plural secret names and code lists, compared with v5.36.8 in a booted SQLite app; the QA ladder on the SQLite labs in every tier, the E2E harness and one large open-source app, booted; the unit suite on Ruby 3.4 and on Rails 7.0 (Ruby 3.1); and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.37.0 release QA: each fix's example failing on the code before it; every answer that changed from v5.36.9 on copies of three large open-source apps, three small sample apps and one private app, static and booted, read against the app's source and this changelog; the schema replay against what PostgreSQL 16, MySQL 8.4, MariaDB 11.4 and SQLite build and Rails dumps from the same migrations; static routes against a live RouteSet on Rails 7.0 and 8.1; views against booted Rails, and constant lookups against Ruby's own; the QA ladder's regression checks in the booted, static and failed-boot tiers and its answer checks; the security advisories' repros; the E2E harness; the unit suite at seeds 1, 27377 and 90210 and in defined order, and on Ruby 3.1 to 4.0 with Rails 7.0 to 8.1; and the unit suite and the E2E harness on Linux, macOS and Windows. In the v5.38.0 release QA: each fix's example failing on the code before it; the migration replay's key order against SQLite on Rails 7.0 to 8.1 and against PostgreSQL 16, MySQL 8.4 and MariaDB 10.6 and 11.4, with the advisor's rollbacks run on each; generated tests run in RSpec and minitest apps with and without factories and fixtures, booted and static, over thirty model shapes; the source redaction over a corpus of more than a thousand credential forms and its cost on Ruby 3.1 and 3.4; the plugin's router on folders of apps, a non-app folder and the gem's own checkout; every answer on one private app and copies of three large open-source apps, booted and static; the unit suite at seeds 1, 27377 and 90210 and in defined order, on Ruby 3.1 to 4.0 with Rails 7.0 to 8.1; and the unit suite and the E2E harness on Linux, macOS and Windows.
  2. Non-crash coverage for every built-in tool including get_view in spec/e2e/in_gemfile_install_spec.rb's full-tool sweep; output correctness (ivar cross-check, render-form detection, partial interfaces) verified against the real blog app in the v5.14.0 release QA.
  3. Unit specs for the "not applicable" API-only messaging: get_view_spec.rb, get_component_catalog_spec.rb, get_turbo_map_spec.rb, get_partial_interface_spec.rb, get_stimulus_spec.rb, get_api_spec.rb; real Rails 8 --api app (api_app) in the v5.14.0 release QA.
  4. spec/lib/rails_ai_context/introspectors/schema_introspector_spec.rb ("with a valid structure.sql fixture", "with a MySQL (mysqldump) structure.sql", "with a SQLite structure.sql"). Unit-proven only - no committed e2e fixture writes a structure.sql file. The MySQL/Trilogy adapter itself (live queries, not the dump format) was also run for real against MySQL 9.6 in the v5.14.0 release QA (store app, schema_format = :sql).
  5. spec/e2e/shapes_spec.rb: "packs and engines code discovery (static tier)" and "multi-database schema dumps".
  6. spec/e2e/shapes_spec.rb: "Mongoid app (bare directory, --app-path, no boot possible)".
  7. spec/e2e/boot_resilience_spec.rb (five boot-failure modes: raises, prints to stdout, writes via the STDOUT constant, calls exit/abort, hangs past the timeout) plus spec/e2e/static_tier_spec.rb ("broken-boot app over the CLI", "broken-boot app over MCP stdio", "syntax error in one model file").
  8. views is files-only (lib/rails_ai_context/introspectors/view_introspector.rb), so its call runs unchanged with nothing booted, proven against spec/fixtures/static_app in spec/lib/rails_ai_context/introspectors/static_tier_declarations_spec.rb. Introspector#run_introspector reports { unavailable: reason } only for the eight runtime-only sections, regardless of shape.
  9. spec/e2e/empty_app_spec.rb - all 35 built-in tools swept against an app with no scaffold, no models, no controllers beyond ApplicationController, no routes beyond root.
  10. spec/e2e/massive_app_spec.rb - schema, model_details, and routes (plus context, onboard, analyze_feature, get_turbo_map and the env section of get_config) sampled against 500 generated models/tables under a 180-second cap.

rails_query, rails_runtime_info and the row estimates run on three database families: PostgreSQL (with PostGIS), MySQL (mysql2 and Trilogy, with MariaDB) and SQLite. rails_query refuses any other adapter (SQL Server, Oracle, CockroachDB) by name before sending a statement, and the table sizes, row estimates and index usage say they are not available there. CI's database_servers job runs the families against PostgreSQL 16, MySQL 8.0 and 8.4 and MariaDB 10.6 and 11.4 on Rails 8.0, through mysql2 and Trilogy. It also runs PostgreSQL 16 with MySQL 8.0 and MariaDB 10.6 on Rails 7.0, which has no Trilogy adapter, and with MySQL 8.4 and MariaDB 11.4 on Rails 8.1; see CONTRIBUTING.md. The postgresql_versions job runs the PostgreSQL examples on PostgreSQL 13, 14, 15, 17 and 18 as well, since the plan checks read the plan the server prints and its shape changes between major versions.

Postgres itself - live query safety (read-only transactions, the BLOCKED_FUNCTIONS guard against pg_read_file/dblink/COPY ... PROGRAM, DDL rejection) alongside schema/routes - is proven end-to-end against a real Postgres instance in spec/e2e/postgres_install_spec.rb, opt-in via TEST_POSTGRES=1 and skipped otherwise.

Known limits



Back to README