rails-ai-context 5.21.3 → 5.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +593 -0
- data/CONTEXT.md +53 -1
- data/CONTRIBUTING.md +8 -0
- data/README.md +195 -467
- data/docs/ARCHITECTURE.md +26 -4
- data/docs/CLI.md +23 -0
- data/docs/COMPATIBILITY.md +31 -14
- data/docs/CONFIGURATION.md +4 -4
- data/docs/FAQ.md +1 -1
- data/docs/GUIDE.md +9 -7
- data/docs/INTROSPECTORS.md +7 -6
- data/docs/TOOLS.md +4 -1
- data/exe/rails-ai-context +90 -370
- data/gemfiles/mcp_floor.gemfile +13 -0
- data/gemfiles/prism_floor.gemfile +1 -10
- data/gemfiles/prism_head.gemfile +1 -10
- data/gemfiles/rails.gemfile +34 -0
- data/lib/generators/rails_ai_context/install/install_generator.rb +43 -154
- data/lib/rails_ai_context/action_filters.rb +145 -0
- data/lib/rails_ai_context/app_kind.rb +2 -7
- data/lib/rails_ai_context/ast_cache.rb +13 -10
- data/lib/rails_ai_context/boot_manager.rb +17 -1
- data/lib/rails_ai_context/change_watch.rb +62 -0
- data/lib/rails_ai_context/cli/entry_boot.rb +214 -0
- data/lib/rails_ai_context/cli/tool_runner.rb +1 -1
- data/lib/rails_ai_context/concern_membership.rb +72 -0
- data/lib/rails_ai_context/configuration.rb +102 -15
- data/lib/rails_ai_context/doctor.rb +27 -20
- data/lib/rails_ai_context/engine.rb +6 -0
- data/lib/rails_ai_context/facts_formatter.rb +8 -11
- data/lib/rails_ai_context/fingerprinter.rb +71 -24
- data/lib/rails_ai_context/gem_lock.rb +97 -0
- data/lib/rails_ai_context/hydrators/controller_hydrator.rb +2 -9
- data/lib/rails_ai_context/hydrators/model_hints.rb +28 -0
- data/lib/rails_ai_context/hydrators/schema_hint_builder.rb +1 -8
- data/lib/rails_ai_context/hydrators/view_hydrator.rb +2 -9
- data/lib/rails_ai_context/install/ai_tool.rb +10 -1
- data/lib/rails_ai_context/install/cleanup.rb +1 -0
- data/lib/rails_ai_context/install/initializer_file.rb +32 -0
- data/lib/rails_ai_context/install/program.rb +149 -0
- data/lib/rails_ai_context/install/selection_record.rb +68 -0
- data/lib/rails_ai_context/install_mode.rb +3 -7
- data/lib/rails_ai_context/introspector.rb +21 -2
- data/lib/rails_ai_context/introspectors/action_resolver.rb +263 -0
- data/lib/rails_ai_context/introspectors/action_text_introspector.rb +2 -6
- data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +6 -18
- data/lib/rails_ai_context/introspectors/active_support_introspector.rb +1 -0
- data/lib/rails_ai_context/introspectors/api_introspector.rb +26 -36
- data/lib/rails_ai_context/introspectors/asset_pipeline_introspector.rb +9 -13
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +18 -45
- data/lib/rails_ai_context/introspectors/config_introspector.rb +17 -22
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +80 -179
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +37 -75
- data/lib/rails_ai_context/introspectors/declared_constant.rb +96 -0
- data/lib/rails_ai_context/introspectors/eager_load.rb +65 -0
- data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +6 -4
- data/lib/rails_ai_context/introspectors/gem_introspector.rb +12 -35
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +93 -11
- data/lib/rails_ai_context/introspectors/job_introspector.rb +90 -149
- data/lib/rails_ai_context/introspectors/listeners/method_call_listener.rb +31 -0
- data/lib/rails_ai_context/introspectors/listeners/methods_listener.rb +37 -5
- data/lib/rails_ai_context/introspectors/listeners/migration_dsl_listener.rb +11 -0
- data/lib/rails_ai_context/introspectors/middleware_introspector.rb +14 -1
- data/lib/rails_ai_context/introspectors/migration_introspector.rb +32 -35
- data/lib/rails_ai_context/introspectors/migration_replay.rb +272 -0
- data/lib/rails_ai_context/introspectors/model_introspector.rb +177 -100
- data/lib/rails_ai_context/introspectors/multi_database_introspector.rb +13 -21
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +14 -40
- data/lib/rails_ai_context/introspectors/route_introspector.rb +3 -0
- data/lib/rails_ai_context/introspectors/schema_conventions.rb +101 -0
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +21 -525
- data/lib/rails_ai_context/introspectors/schema_reader.rb +69 -2
- data/lib/rails_ai_context/introspectors/security_introspector.rb +3 -6
- data/lib/rails_ai_context/introspectors/source_scan.rb +80 -0
- data/lib/rails_ai_context/introspectors/structure_sql_reader.rb +196 -0
- data/lib/rails_ai_context/introspectors/test_introspector.rb +7 -17
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +276 -120
- data/lib/rails_ai_context/introspectors/view_introspector.rb +2 -1
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +19 -0
- data/lib/rails_ai_context/legacy_cleanup.rb +5 -0
- data/lib/rails_ai_context/live_reload.rb +37 -50
- data/lib/rails_ai_context/mcp_config_generator.rb +2 -2
- data/lib/rails_ai_context/mcp_edge.rb +25 -3
- data/lib/rails_ai_context/middleware.rb +1 -13
- data/lib/rails_ai_context/payload.rb +178 -0
- data/lib/rails_ai_context/pending_migrations.rb +60 -0
- data/lib/rails_ai_context/presets.rb +34 -4
- data/lib/rails_ai_context/redaction.rb +32 -1
- data/lib/rails_ai_context/resources.rb +6 -13
- data/lib/rails_ai_context/route_coverage.rb +30 -1
- data/lib/rails_ai_context/safe_path.rb +72 -0
- data/lib/rails_ai_context/schema_version.rb +13 -0
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +17 -22
- data/lib/rails_ai_context/serializers/compact_serializer_helper.rb +29 -49
- data/lib/rails_ai_context/serializers/context_file_serializer.rb +30 -3
- data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +15 -20
- data/lib/rails_ai_context/serializers/copilot_serializer.rb +19 -27
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +15 -20
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +65 -88
- data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +15 -24
- data/lib/rails_ai_context/serializers/opencode_serializer.rb +5 -29
- data/lib/rails_ai_context/serializers/section_facts.rb +66 -0
- data/lib/rails_ai_context/serializers/section_marker_writer.rb +11 -1
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +67 -79
- data/lib/rails_ai_context/serializers/test_command_detection.rb +1 -2
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +1 -2
- data/lib/rails_ai_context/server.rb +4 -12
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +61 -185
- data/lib/rails_ai_context/tools/analyze_feature.rb +37 -53
- data/lib/rails_ai_context/tools/base_tool.rb +68 -24
- data/lib/rails_ai_context/tools/dependency_graph.rb +3 -4
- data/lib/rails_ai_context/tools/diagnose.rb +27 -21
- data/lib/rails_ai_context/tools/generate_test.rb +12 -4
- data/lib/rails_ai_context/tools/get_api.rb +65 -12
- data/lib/rails_ai_context/tools/get_callbacks.rb +3 -4
- data/lib/rails_ai_context/tools/get_concern.rb +36 -136
- data/lib/rails_ai_context/tools/get_config.rb +16 -14
- data/lib/rails_ai_context/tools/get_context.rb +62 -123
- data/lib/rails_ai_context/tools/get_controllers.rb +50 -181
- data/lib/rails_ai_context/tools/get_edit_context.rb +9 -37
- data/lib/rails_ai_context/tools/get_env.rb +6 -35
- data/lib/rails_ai_context/tools/get_frontend_stack.rb +12 -15
- data/lib/rails_ai_context/tools/get_helper_methods.rb +7 -23
- data/lib/rails_ai_context/tools/get_i18n.rb +50 -5
- data/lib/rails_ai_context/tools/get_job_pattern.rb +96 -165
- data/lib/rails_ai_context/tools/get_model_details.rb +33 -70
- data/lib/rails_ai_context/tools/get_partial_interface.rb +16 -40
- data/lib/rails_ai_context/tools/get_routes.rb +5 -9
- data/lib/rails_ai_context/tools/get_schema.rb +33 -33
- data/lib/rails_ai_context/tools/get_service_pattern.rb +2 -2
- data/lib/rails_ai_context/tools/get_stimulus.rb +5 -22
- data/lib/rails_ai_context/tools/get_test_info.rb +49 -23
- data/lib/rails_ai_context/tools/get_turbo_map.rb +113 -454
- data/lib/rails_ai_context/tools/get_view.rb +29 -144
- data/lib/rails_ai_context/tools/migration_advisor.rb +6 -10
- data/lib/rails_ai_context/tools/onboard.rb +90 -113
- data/lib/rails_ai_context/tools/performance_check.rb +5 -3
- data/lib/rails_ai_context/tools/read_logs.rb +13 -43
- data/lib/rails_ai_context/tools/review_changes.rb +4 -7
- data/lib/rails_ai_context/tools/runtime_info.rb +23 -11
- data/lib/rails_ai_context/tools/safe_call.rb +4 -4
- data/lib/rails_ai_context/tools/search_code.rb +19 -8
- data/lib/rails_ai_context/tools/search_docs.rb +3 -8
- data/lib/rails_ai_context/tools/validate.rb +8 -45
- data/lib/rails_ai_context/tools/validate_semantics.rb +37 -34
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context/vfs.rb +44 -102
- data/lib/rails_ai_context/view_file.rb +59 -0
- data/lib/rails_ai_context/watcher.rb +17 -32
- data/lib/rails_ai_context.rb +12 -8
- data/server.json +1 -1
- metadata +26 -4
- data/lib/rails_ai_context/source_line.rb +0 -208
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d80b8bd17147a84fb1f8ce3028b7e1291af63c0d639663704eec171069e8f1b7
|
|
4
|
+
data.tar.gz: 4ce32bd09b90f16b62f70621642d6db35258c3a47892196e1fdb6d329366b4ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53ecb39c98209ddd59050431dca51b448c848e7f6e4d93fe092523ee87b014e6279aa25d97b283e57c66368eb0b315baaa4b75d0ea349d21bc6b0b1109ec0d2d
|
|
7
|
+
data.tar.gz: 25c7854ce8e4e4c1e16996e13a2031dc35d6cfee245d46515a48833c4fdcc48d35eb3a38b5365cfa4518847096340a104dc79dbfda2dfc314565a1508ea5dd7b
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,599 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [5.25.0] - 2026-09-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`sprockets-rails`, `sprockets`, `sorcery` and `clearance` join the notable
|
|
13
|
+
gem table**, so `rails_get_gems` lists them and the `rails_get_config` assets
|
|
14
|
+
and auth lines name them from the lockfile rather than from a loaded
|
|
15
|
+
constant.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
Defects found by a second survey round over the whole surface, and the
|
|
20
|
+
duplicated mechanisms behind them.
|
|
21
|
+
|
|
22
|
+
- **`max_view_total_size` and `max_view_file_size` said they capped view
|
|
23
|
+
reads.** Only `doctor` reads either one, as the threshold for its view-size
|
|
24
|
+
warning; the config comments and the docs rows now say that.
|
|
25
|
+
- **`excluded_concerns` hid a concern from a model's list but not from the
|
|
26
|
+
catalogue.** `rails_get_concern` and the `rails_get_active_support` concern
|
|
27
|
+
registry still listed and counted a concern the key names; all three now
|
|
28
|
+
apply the same predicate.
|
|
29
|
+
- **An in-Gemfile app's `.rails-ai-context.yml` was inert.** The generated
|
|
30
|
+
initializer holds a `configure` block, and the YAML was skipped whenever one
|
|
31
|
+
had run, so a booted command took the defaults while `--no-boot` read the
|
|
32
|
+
file and the two disagreed (45 tools against 43 with `skip_tools` set).
|
|
33
|
+
Precedence is a merge: the file is applied once, before
|
|
34
|
+
`config/initializers`, so an initializer may assign a key or edit it in
|
|
35
|
+
place and both survive; a later call from the standalone binary or the
|
|
36
|
+
CLI's boot path is a no-op. A block wins the keys it assigns wherever it
|
|
37
|
+
lives, so one in `config/application.rb` or an environment file, which runs
|
|
38
|
+
before the load, keeps them too.
|
|
39
|
+
- **The static tier named the app after its directory.** `onboard` and every
|
|
40
|
+
generated context file were headed "mastodon" for an app that declares
|
|
41
|
+
`module Mastodon`. The name now comes from the module enclosing
|
|
42
|
+
`class Application < Rails::Application` in `config/application.rb`, and
|
|
43
|
+
falls back to the directory only when that file names nothing.
|
|
44
|
+
- **The static tier counted every class under `app/models` as a model.**
|
|
45
|
+
Namespace modules, form objects, filters and plain service classes were
|
|
46
|
+
listed and rendered as models of a table (Mastodon answered 195 models
|
|
47
|
+
against 117 booted). A static model is now a class whose superclass chain
|
|
48
|
+
reaches `ApplicationRecord`, a namespaced `*ApplicationRecord` or
|
|
49
|
+
`ActiveRecord::Base`, STI subclasses included.
|
|
50
|
+
- **An unreadable `.rails-ai-context.yml` aborted the boot.** A directory or a
|
|
51
|
+
file the process cannot read at that path raised out of the engine
|
|
52
|
+
initializer; it now warns and keeps the defaults, the way broken YAML does.
|
|
53
|
+
- **A model under a per-connection abstract base was missing from the static
|
|
54
|
+
list.** The multi-database shape - `class AnimalsRecord < ApplicationRecord;
|
|
55
|
+
self.abstract_class = true` in its own file, then `class Dog <
|
|
56
|
+
AnimalsRecord` - dropped every model on that connection from the static
|
|
57
|
+
list, the count and the schema listing. An abstract base is still walked so
|
|
58
|
+
chains resolve through it; it is left out of the result, as before.
|
|
59
|
+
- **`rails_get_api` stated a filesystem finding for a section nobody had
|
|
60
|
+
read.** In the static tier the whole section but the mode was declared
|
|
61
|
+
unavailable, and nothing read that declaration, so an app with
|
|
62
|
+
`app/controllers/api/v1/` was told "not detected (no app/controllers/api/v*
|
|
63
|
+
directories)". Every detection in that section is a file read, so the
|
|
64
|
+
static tier now answers all of them, and a key a section does name as
|
|
65
|
+
unanswered renders as `[UNAVAILABLE: ...]` rather than as a negative
|
|
66
|
+
finding.
|
|
67
|
+
- **An existing `.ai-context.json` holding anything but a JSON object
|
|
68
|
+
aborted the whole generation run.** The skip check reads it as a file to
|
|
69
|
+
replace.
|
|
70
|
+
- **`rails_get_schema`'s table heading named every model on the table**, so
|
|
71
|
+
an STI table with thirty subclasses filled the cheap summary with one
|
|
72
|
+
heading. It names five and counts the rest, the way the file's other lists
|
|
73
|
+
do.
|
|
74
|
+
- **`rails_get_test_info` blamed the app root for a name it refused as
|
|
75
|
+
sensitive.** The refusal names what the check covers: the name leaves the
|
|
76
|
+
app root or names a sensitive file.
|
|
77
|
+
- **`rails_onboard`'s Getting Started block told the reader to `cd` into the
|
|
78
|
+
app's class name underscored**, which is not the directory the clone lands
|
|
79
|
+
in. It names the app directory.
|
|
80
|
+
- **`init` refused a tree with app source but no `config/environment.rb`
|
|
81
|
+
after it had already written the config files**, leaving it half set up
|
|
82
|
+
with no `CLAUDE.md`. Every command that can serve the static tier now
|
|
83
|
+
reads such a tree, the way `--no-boot` already did.
|
|
84
|
+
- **A tree with app source but no `config/environment.rb` printed a boot
|
|
85
|
+
failure before falling back to static analysis.** No boot can succeed
|
|
86
|
+
without that file, so the static tier now takes over at once and its banner
|
|
87
|
+
names the missing file; `doctor` refuses the same tree with that reason.
|
|
88
|
+
The banner calls that tree static mode rather than a boot failure, since
|
|
89
|
+
no boot ran.
|
|
90
|
+
- **`rails_runtime_info` and `rails_query` told a tree with no
|
|
91
|
+
`config/environment.rb` to fix a boot failure or drop `--no-boot`**, neither
|
|
92
|
+
of which had happened. Such a tree now reads "This tree has no
|
|
93
|
+
`config/environment.rb`; add one (or run from the app root) for runtime
|
|
94
|
+
data."
|
|
95
|
+
- **A source path was contained without a separator and against an
|
|
96
|
+
unresolved root.** The frontend framework introspector's own containment
|
|
97
|
+
check let `/app-old` pass for `/app`. The pre-v5.8.1 bug, still in one
|
|
98
|
+
place.
|
|
99
|
+
- **`rails_get_env` printed Dockerfile `ENV` and `ARG` values verbatim at
|
|
100
|
+
detail full.** Every default now leaves through the redaction gate, and a
|
|
101
|
+
surrounding quote pair is stripped, so `ENV FOO="bar"` prints `bar`.
|
|
102
|
+
- **`rails_read_logs` matched the `search` term before redacting**, so a
|
|
103
|
+
hidden value could be probed a character at a time. The search runs on the
|
|
104
|
+
redacted text now.
|
|
105
|
+
- **The middleware introspector fabricated an empty stack in the static
|
|
106
|
+
tier.** It declares an alternate source and answers only its file facts,
|
|
107
|
+
rather than reporting an app with no middleware.
|
|
108
|
+
- **`rails_get_config` and `rails-ai-context facts` read gem keys no
|
|
109
|
+
introspector emits**, so the Auth line and the Key Dependencies section
|
|
110
|
+
never rendered at all.
|
|
111
|
+
- **Jobs and mailers carry `file:`.** `rails_get_job_pattern` reads the
|
|
112
|
+
carried file instead of camelizing a basename, and its listing comes from
|
|
113
|
+
the payload, so a job in a pack lists.
|
|
114
|
+
- **The auth introspector keyed Devise models by basename**, so
|
|
115
|
+
`app/models/admin/user.rb` overwrote `User`.
|
|
116
|
+
- **An out-of-order migration merge was reported as none pending** by the
|
|
117
|
+
migrations section while the schema section counted it. Both derive from
|
|
118
|
+
one applied set now, and an unknown applied set answers no pending key
|
|
119
|
+
rather than "everything" or "nothing".
|
|
120
|
+
- **Substring lockfile scans reported `bugsnag-capistrano` as Bugsnag** and
|
|
121
|
+
`database_cleaner-redis` as database_cleaner. One lockfile reader with
|
|
122
|
+
exact names answers every gem question, across the GEM, GIT and PATH
|
|
123
|
+
sections.
|
|
124
|
+
- **The hydrators warned about a model they had just resolved in another
|
|
125
|
+
spelling**, and emitted one hint block twice.
|
|
126
|
+
- **The channel and mailer eager loads had no per-constant recovery**, so a
|
|
127
|
+
single unloadable file emptied the list.
|
|
128
|
+
- **`YAML.safe_load` refused the `&default` anchors** every stock webpacker
|
|
129
|
+
or shakapacker config carries, so the source path fell back to the
|
|
130
|
+
convention.
|
|
131
|
+
- **VFS: `controllers/admin/posts` fell into the action handler** and
|
|
132
|
+
`routes/PostsController` returned zero routes.
|
|
133
|
+
- **`Payload.section` accepted a refused (`unavailable`) section**, so a
|
|
134
|
+
static-tier context file rendered a bare heading.
|
|
135
|
+
- **Every generated file counts the same controller set.**
|
|
136
|
+
`config.excluded_controllers` is honoured everywhere, not only by
|
|
137
|
+
`rails_get_controllers`.
|
|
138
|
+
- **The doctor counted models and controllers under `app/` only.** Packs and
|
|
139
|
+
engines count, and the freshness check reads the one watch scope.
|
|
140
|
+
- **The watcher stopped noticing `config/routes.rb` and `db/schema.rb`.** The
|
|
141
|
+
fingerprint, the watcher and the doctor share one scope covering `config`
|
|
142
|
+
and `db` whole, and the fingerprint mark is taken before the read it
|
|
143
|
+
protects.
|
|
144
|
+
- **The diagnose tool's git probe ran outside a repository** and leaked its
|
|
145
|
+
stderr into the response.
|
|
146
|
+
- **`rails_get_stimulus` re-read every controller file** to recompute a
|
|
147
|
+
lifecycle the payload already carried.
|
|
148
|
+
- **MCP config removal wrote non-atomically**, the dead `AstCache.invalidate`
|
|
149
|
+
is gone, and the doctor checks that `.codex/config.toml` is gitignored.
|
|
150
|
+
- **Composing tools decided whether a sub-tool answered by scraping its prose
|
|
151
|
+
for "not found".** A real answer whose body mentioned those words was
|
|
152
|
+
dropped. Responses carry an empty marker in MCP `meta` now.
|
|
153
|
+
- **`rails_get_test_info` guarded a caller-supplied name with its own
|
|
154
|
+
containment.** It reads through the shared guard.
|
|
155
|
+
- **The asset pipeline introspector's literal `none` was rendered as if it
|
|
156
|
+
named a pipeline**, so every generated context file carried
|
|
157
|
+
`- Assets: none`. The line shows only the parts that name something, and
|
|
158
|
+
disappears when there are none.
|
|
159
|
+
- **`rails_get_view`'s ivar list counted an `@` inside an email address and a
|
|
160
|
+
`@@class_variable`.** Neither is an instance variable.
|
|
161
|
+
- **A controller ivar compared, not assigned, was reported as set.** `return
|
|
162
|
+
unless @post == current_user` no longer names `@post`; `||=`, `+=`, `<<=` and
|
|
163
|
+
`>>=` still count.
|
|
164
|
+
- **`rails_get_context` resolved an action name case-sensitively** while
|
|
165
|
+
`rails_get_controllers` did not, so `action: "Show"` skipped the ivar
|
|
166
|
+
cross-check.
|
|
167
|
+
- **The ivar cross-check ran with no view templates section**, reporting every
|
|
168
|
+
controller ivar as unused in the view. It is skipped instead.
|
|
169
|
+
- **`rails_get_turbo_map` printed a Turbo Stream response as a Ruby hash.**
|
|
170
|
+
It renders `PostsController#create`, the way the file's other sections name
|
|
171
|
+
a controller action.
|
|
172
|
+
- **A second `context` run rewrote the generated files** for their timestamp
|
|
173
|
+
alone, so a repo that commits the context files saw a diff from a run that
|
|
174
|
+
found nothing new. One rule now covers them all: a file that differs only
|
|
175
|
+
in its `generated_at` key, or in the full-mode header's `> Generated:`
|
|
176
|
+
line, is skipped.
|
|
177
|
+
- **The schema listing named one model per table.** A table an STI child or a
|
|
178
|
+
namespaced second model shares listed whichever came first in the payload,
|
|
179
|
+
which could be the emptier one. Every model on the table is listed now,
|
|
180
|
+
the one carrying the most detail first.
|
|
181
|
+
- **Every static-tier context file carried `Rails [UNAVAILABLE: app not
|
|
182
|
+
booted]` mid-sentence.** The version comes from the lockfile, and the
|
|
183
|
+
marker is left for a tree whose lockfile does not carry rails.
|
|
184
|
+
- **`rails-ai-context preset " FULL "` exited 1 with the listing.** A preset
|
|
185
|
+
name is matched whatever its case or padding.
|
|
186
|
+
- **A `--no-boot` run that found no app printed the doctor hint**, which asks
|
|
187
|
+
for a boot error that never happened. `doctor` and `init` also accept a
|
|
188
|
+
tree that has app source but no `config/environment.rb`, and the refusal
|
|
189
|
+
names that file instead of telling the user to go to the app root they are
|
|
190
|
+
standing in.
|
|
191
|
+
|
|
192
|
+
### Changed
|
|
193
|
+
|
|
194
|
+
- **`RailsAiContext.generate_context(format: nil)` means the recorded AI-tool
|
|
195
|
+
selection**, and all of them when nothing is recorded. It was `:all`.
|
|
196
|
+
- **Static-tier migration names are the class-style name** (`CreatePosts`),
|
|
197
|
+
matching what the booted tier reports.
|
|
198
|
+
- **Names in the turbo, auth, attachments and multi-database sections carry
|
|
199
|
+
their namespace** (`Admin::User`, not `User`), and packs and in-repo
|
|
200
|
+
engines count everywhere the app's source is walked.
|
|
201
|
+
- **The `.env.example` reader judges a value by its shape and length only.** A
|
|
202
|
+
default in Ruby source or a Dockerfile is condemned by a secret-shaped name
|
|
203
|
+
as well.
|
|
204
|
+
- **Turbo wiring lives in the introspector payload with file and line** -
|
|
205
|
+
frames, model broadcasts, explicit broadcasts and stream subscriptions -
|
|
206
|
+
and `rails_get_turbo_map` renders it. A `turbo_frame_tag dom_id(@post,
|
|
207
|
+
:edit)` frame renders as that call, and a symbol `turbo_stream_from :posts`
|
|
208
|
+
now pairs with its broadcast. A subscription argument that carries its own
|
|
209
|
+
commas renders whole: `turbo_stream_from [current_user, :notifications]` and
|
|
210
|
+
`dom_id(@post, :x)` keep every character they were written with.
|
|
211
|
+
- **The public-methods lists come from the parser.** `rails_get_concern`,
|
|
212
|
+
`rails_get_helper_methods`, `rails_get_model_details` and
|
|
213
|
+
`rails_get_controllers` read `private def` and `class_methods do`
|
|
214
|
+
correctly.
|
|
215
|
+
- **The standalone binary boots through `CLI::EntryBoot`**, and presets run
|
|
216
|
+
through `Presets.run` in both the binary and the rake task.
|
|
217
|
+
- **`rails_get_controllers` and the controller-action resource decide which
|
|
218
|
+
filters apply through one module, `ActionFilters`.** Every inherited filter
|
|
219
|
+
carries the `(from Parent)` annotation, after_action and
|
|
220
|
+
except-constrained ones included; a filter constrained with `except:` shows
|
|
221
|
+
that constraint; `skip_after_action` and `skip_around_action` count as
|
|
222
|
+
skips, and a skipped filter is struck through in the whole-controller view
|
|
223
|
+
as well as the per-action one.
|
|
224
|
+
- **A config value longer than 40 characters is filtered on every path.** The
|
|
225
|
+
Ruby-source and Dockerfile readers capped at 30 before; `.env.example` was
|
|
226
|
+
already 40.
|
|
227
|
+
- **`schema[:pending_migrations]`, reachable through the `rails://schema`
|
|
228
|
+
resource, is a list of `{ version:, name: }` entries** rather than version
|
|
229
|
+
strings.
|
|
230
|
+
- **`rails-ai-context://routes/{controller}` prefers an exact route-key
|
|
231
|
+
match**, so `routes/posts` no longer also returns `admin/posts`.
|
|
232
|
+
- **`unavailable_sections`, where a section carries it, is a list of
|
|
233
|
+
section keys**, not a reason string. The middleware section's static
|
|
234
|
+
answer is the one that still emits it.
|
|
235
|
+
- **The models resource template advertises
|
|
236
|
+
`rails-ai-context://models/{name}`**, and every scheme resolves through the
|
|
237
|
+
VFS rather than an exact-match legacy reader. `rails://models/{name}` is
|
|
238
|
+
still accepted.
|
|
239
|
+
- **`controllers[:controllers]` can carry `{ error: "unreadable" }` entries**
|
|
240
|
+
for a file over the size cap or otherwise unreadable, and the listing and
|
|
241
|
+
the count include them.
|
|
242
|
+
|
|
243
|
+
### Removed
|
|
244
|
+
|
|
245
|
+
- **`SourceLine`, the hand-rolled Ruby lexer.** Every source read goes through
|
|
246
|
+
Prism.
|
|
247
|
+
- **`Fingerprinter.reset_gem_lib_fingerprint!` and `AstCache.invalidate`** -
|
|
248
|
+
no callers.
|
|
249
|
+
- **Support for the mcp gem below 0.13.** The gemspec floor is `>= 0.13`, the
|
|
250
|
+
first release whose tool responses carry `meta`.
|
|
251
|
+
- **`Presets.names` and `Presets.fetch`** - the listing and the run read
|
|
252
|
+
`DEFINITIONS` directly.
|
|
253
|
+
- **`SchemaHintBuilder.build_many`** - no callers.
|
|
254
|
+
- **`Fingerprinter.changed?`** - no callers; `stale?` answers the question.
|
|
255
|
+
- **The public constant `ChangeWatch::WATCH_DIRS`.**
|
|
256
|
+
- **`RailsAiContext.configured_via_block?`** - no callers once the config file
|
|
257
|
+
applies once per configuration and a block wins its keys by name.
|
|
258
|
+
|
|
259
|
+
## [5.24.0] - 2026-08-17
|
|
260
|
+
|
|
261
|
+
### Fixed
|
|
262
|
+
|
|
263
|
+
Defects found by a QA round against GitLab, OpenProject, Canvas LMS,
|
|
264
|
+
Discourse and Mastodon. Every one of them exits 0.
|
|
265
|
+
|
|
266
|
+
- **The commands that read the app degrade the way `tool` does.** `context`,
|
|
267
|
+
`inspect`, `facts`, `preset`, `watch` and `init` called a bare boot guard,
|
|
268
|
+
so on a repo you have just cloned - the case an agent most needs a
|
|
269
|
+
`CLAUDE.md` for, and the case least likely to boot - every tool answered
|
|
270
|
+
and the command that writes the files exited 1 having written nothing.
|
|
271
|
+
`init` was worse: it writes its config files first, so a boot failure left
|
|
272
|
+
the app half set up and called that a failure. They allow the static tier
|
|
273
|
+
now and each takes `--no-boot`; `doctor` still fails, because diagnosing
|
|
274
|
+
the boot is its job. Writing under `--no-boot` then exposed its own bug -
|
|
275
|
+
the context writer asked `Rails.application` for the output directory,
|
|
276
|
+
which raises `NameError` on the path where Rails is never loaded at all.
|
|
277
|
+
- **`tool --list` reads the app's config when the app cannot boot.** Boot is
|
|
278
|
+
what normally loads `.rails-ai-context.yml`, so the listing fell back to
|
|
279
|
+
the gem's defaults: on Mastodon it advertised 45 tools while the MCP server
|
|
280
|
+
offered 43 and the CLI itself answered `Unknown tool 'query'` for one it
|
|
281
|
+
had just listed.
|
|
282
|
+
- **`search_extensions` is documented as the fallback's list, which is what
|
|
283
|
+
it is.** Making ripgrep honour it did make the two backends agree, and it
|
|
284
|
+
cost the reach that makes the tool useful: on Mastodon `gem 'devise'` went
|
|
285
|
+
from 5 results to none, because a Gemfile carries no listed extension - and
|
|
286
|
+
the same for a Rakefile, a `.md`, a `.sql`. The docs, the attr comment and
|
|
287
|
+
the line the install generator writes now say plainly that the list is the
|
|
288
|
+
Ruby fallback's and that ripgrep searches every file.
|
|
289
|
+
- **A table the replay cannot name is not reported.** Canvas has a migration
|
|
290
|
+
that calls `create_table table_name do |t|` with a local computed at run
|
|
291
|
+
time; the replay kept the entry under a nil key, and the first serializer
|
|
292
|
+
to sort the table names took the whole context run down. Reachable only
|
|
293
|
+
once `context` could enter the static tier at all.
|
|
294
|
+
- **Coverage says nothing when there is nothing to measure against.** With a
|
|
295
|
+
`default_locale` the app configures but ships no file for, every locale
|
|
296
|
+
scored zero and all of them were named as untranslated.
|
|
297
|
+
- **A mixin in a nested concerns directory is not a model.** The skip only saw
|
|
298
|
+
the top-level `concerns/` that Rails autoloads, so OpenProject's
|
|
299
|
+
`app/models/queries/operators/concerns` contributed four mixins to a model
|
|
300
|
+
count of 978 where the app has 974. A nested `concerns/` is an ordinary
|
|
301
|
+
namespace, though, so the directory name alone does not decide it: a file
|
|
302
|
+
there that declares a class is a model like any other.
|
|
303
|
+
- **`docs/COMPATIBILITY.md` describes the static tier the gem actually has.**
|
|
304
|
+
It said 6 introspectors answer without a booted app and "the other 34 have
|
|
305
|
+
no static path", naming eight examples - all of which answer. The real
|
|
306
|
+
split is 23 files-only, 9 alternate-source and 8 runtime-only: 32 of 40.
|
|
307
|
+
A guard spec derives all three lists from `INTROSPECTOR_MAP`.
|
|
308
|
+
|
|
309
|
+
- **A controller is named by the constant its source declares.** Zeitwerk
|
|
310
|
+
resolves a path through the app's own inflector, which the static tier
|
|
311
|
+
never loads, so camelizing the path invented `Activitypub::` for the 12
|
|
312
|
+
controllers Mastodon declares as `ActivityPub::` and `Oauth::` for the 4
|
|
313
|
+
it declares as `OAuth::` - names that appear nowhere in an app that
|
|
314
|
+
registers those acronyms, and a `NameError` for anyone who uses one. The
|
|
315
|
+
path stays the answer where the source does not carry the whole name
|
|
316
|
+
(`application_cable/channel.rb`) and where the declared name does not name
|
|
317
|
+
that file, since Prism recovers a syntax error into a partial tree.
|
|
318
|
+
- **An interceptor is told from a mailer by its framework hook.**
|
|
319
|
+
`app/mailers` is also where ActionMailer interceptors live, and every `.rb`
|
|
320
|
+
under it counted: OpenProject's `Interceptors::DefaultHeaders` arrived as a
|
|
321
|
+
mailer with `delivering_email` - a hook the framework calls - listed as an
|
|
322
|
+
email an agent could send, 2 of its 11 entries. What an interceptor has and
|
|
323
|
+
a mailer does not is one of `delivering_email`, `previewing_email` or
|
|
324
|
+
`delivered_email`; requiring a class instead would have dropped GitLab's 20
|
|
325
|
+
`Emails::*` modules, which hold every notification it sends.
|
|
326
|
+
- **Migration replay reads what a migration does, not what it undoes.** A
|
|
327
|
+
`def down` says how to reverse the change, so replaying it alongside `up`
|
|
328
|
+
cancelled the migration out. On a migrations-only app the ordinary
|
|
329
|
+
up-creates/down-drops pair erased two tables OpenProject really has (35
|
|
330
|
+
to 37), and the reverse pair would have invented one it dropped. The same
|
|
331
|
+
now holds for the block spellings, `reversible { |dir| dir.down { ... } }`
|
|
332
|
+
and `revert`, and for the `t.timestamps` inside them - those were found by
|
|
333
|
+
a separate walk over the whole file, so a down body's timestamps landed on
|
|
334
|
+
the last table created on the way up.
|
|
335
|
+
- **i18n coverage groups the locales below its own rounding floor instead of
|
|
336
|
+
listing them.** A language-name lookup table under `config/locales`
|
|
337
|
+
contributes a top-level key per language, and Rails does load each as an
|
|
338
|
+
available locale. Scoring them produced a row each saying every key was
|
|
339
|
+
missing: on Discourse, 138 of 186 coverage rows described a translation
|
|
340
|
+
effort nobody had started. Those 138 are now summarised in one line, with a
|
|
341
|
+
shared key count stated once. The rows still exist - asking for one by name
|
|
342
|
+
answers with its numbers, so a translation genuinely started below the floor
|
|
343
|
+
is not written off as untranslated. The available list still matches a
|
|
344
|
+
booted Rails, and a locale's keys are read from its own file and any shared
|
|
345
|
+
file together, so one that lives outside the naming convention is scored.
|
|
346
|
+
- **A recorded tool selection no longer switches off `.ai-context.json`.**
|
|
347
|
+
The install generator always records one, and every later `rails
|
|
348
|
+
ai:context` passed that list to the serializer. The machine artifact is
|
|
349
|
+
not an AI tool and no install menu offers it, so it silently stopped being
|
|
350
|
+
written on the one install path the docs describe - while the generator
|
|
351
|
+
went on adding it to `.gitignore`.
|
|
352
|
+
- **`skip_tools` accepts the symbol spelling its neighbours use.** Every
|
|
353
|
+
other key in the generated initializer takes symbols, and `%i[]` here
|
|
354
|
+
skipped nothing at all: both readers compare against a tool name, which is
|
|
355
|
+
a String.
|
|
356
|
+
|
|
357
|
+
- **A controller carries the file it was read from.** Reading the name from
|
|
358
|
+
the source fixed the name and broke every path derived from it:
|
|
359
|
+
`ActivityPub::CollectionsController`.underscore is `activity_pub/...` and
|
|
360
|
+
the file is under `activitypub/`, so `rails_get_context` answered "Could
|
|
361
|
+
not extract source code" for a file that exists and asked the routes for a
|
|
362
|
+
controller key Rails does not use. Both tiers emit `file:` - the booted one
|
|
363
|
+
asks Ruby where the class was defined - and the consumers read it through
|
|
364
|
+
`Payload`.
|
|
365
|
+
- **The locale files are indexed once, not once per locale.** Asking every
|
|
366
|
+
file about every locale is O(locales x files): on Discourse, 187 over 108,
|
|
367
|
+
it took the i18n answer from 4.6 seconds to four and a half minutes. Each
|
|
368
|
+
entry also records the locales it serves, so asking for one by name finds
|
|
369
|
+
its files even when the filename spells the gem rather than the locale.
|
|
370
|
+
|
|
371
|
+
- **Every consumer that turns a controller name back into a path reads the
|
|
372
|
+
file it was read from.** Carrying `file:` fixed six of them and left five
|
|
373
|
+
answering a confident negative, which is the answer an agent acts on
|
|
374
|
+
without checking. On Mastodon's 18 inflected controllers: `rails_test_info`
|
|
375
|
+
reported 16 specs that exist as missing, `rails_get_routes` reported 4
|
|
376
|
+
routes that exist as absent, `rails_get_view` 2 views, and
|
|
377
|
+
`rails_generate_test` wrote a spec whose body was `skip "no routes found"`
|
|
378
|
+
for a controller with a route. `rails_analyze_feature` listed them as
|
|
379
|
+
untested. `Payload.controller_route_key` is the one derivation now, and a
|
|
380
|
+
spec drives all five tools with an inflected name.
|
|
381
|
+
- **The install path's files require their own stdlib.** `init` loads six
|
|
382
|
+
files before Rails and before the entry file, on purpose - so the entry
|
|
383
|
+
file's `require "set"` never runs for them. `legacy_cleanup`'s `[...].to_set`
|
|
384
|
+
raised `NoMethodError` on Ruby 3.1, where `Set` is not autoloaded, and 3.2+
|
|
385
|
+
hid it: only the CI matrix ever saw it.
|
|
386
|
+
- **`rails_runtime_info` reports cache numbers, not the cache object.** The
|
|
387
|
+
MemoryStore branch passed `cache.inspect` straight through, so the answer
|
|
388
|
+
carried `#<ActiveSupport::Cache::MemoryStore entries=0, size=0, options={...}>`
|
|
389
|
+
- the shape this gem has shipped as a defect before. Entries and size are
|
|
390
|
+
now facts, and a store that answers `#stats` gets its hash rendered as pairs
|
|
391
|
+
rather than a Ruby literal.
|
|
392
|
+
- **A model's table comes from its file, not from its name.** Naming a model by
|
|
393
|
+
the constant its source declares made `underscore` stop being the way back:
|
|
394
|
+
`OAuthClientConfig` underscores to `o_auth_client_config`, and the file is
|
|
395
|
+
`oauth_client_config.rb`. `rails_model_details` reported the table as
|
|
396
|
+
`o_auth_client_configs`, which no app has, so the columns section vanished
|
|
397
|
+
and the schema hint pointed at nothing - on Canvas that hit
|
|
398
|
+
`OAuthClientConfig`, `OAuthRequest`, `AuthenticationProvider::OAuth` and
|
|
399
|
+
`OAuth2`, on OpenProject `OAuthClient` and `OAuthClientToken`. The file's own
|
|
400
|
+
name already carries the inflection, because Zeitwerk resolved the constant
|
|
401
|
+
from it. `rails_generate_test`, `rails_get_callbacks`, `rails_test_info` and
|
|
402
|
+
the five `rails_validate` checks that key models by path read it too.
|
|
403
|
+
- **An abstract base class is not a model in the static tier either.** A
|
|
404
|
+
booted Rails rejects `abstract_class?`, and a namespaced base class is one:
|
|
405
|
+
GitLab's `Ci::ApplicationRecord`, `PackageMetadata::ApplicationRecord` and
|
|
406
|
+
`SecApplicationRecord` were counted, so the same app answered 895 models
|
|
407
|
+
without a boot and 888 with one. OpenProject: 974 against 971.
|
|
408
|
+
- **A model carries its file too, and is named by the constant its source
|
|
409
|
+
declares.** `rails_model_details` rebuilt `app/models/<underscored>.rb` from
|
|
410
|
+
the name in four places, so for a model in a pack or an engine the custom
|
|
411
|
+
validations, the source-defined methods, the method signatures and the class
|
|
412
|
+
structure all went quietly missing from an answer that otherwise looked
|
|
413
|
+
complete. The static tier also camelized the path into the name, so an app
|
|
414
|
+
registering an inflection got a constant it does not have - and one the
|
|
415
|
+
booted tier, which reads the real class, would never agree with.
|
|
416
|
+
- **`rails_validate_semantics` checks the views under an inflected directory.**
|
|
417
|
+
A view directory names the route key, so camelizing `app/views/oauth/` back
|
|
418
|
+
gave a constant the app never declares and the undefined-ivar check stopped
|
|
419
|
+
running for that whole tree without saying so.
|
|
420
|
+
- **A test-gap claim is not made from a scan that stopped early.**
|
|
421
|
+
`rails_analyze_feature` scans the first 500 files per glob; Discourse has
|
|
422
|
+
1,672 specs, so the scan never reached the ones covering the feature and
|
|
423
|
+
every controller in it was reported as having no test. The Tests section
|
|
424
|
+
disclosed the cap, the gaps section asserted through it.
|
|
425
|
+
- **"Global before_actions" in the generated files means global.** The scan
|
|
426
|
+
matched `skip_before_action` too, and ignored `only:` / `except:` / `if:`.
|
|
427
|
+
Mastodon's `CLAUDE.md` listed five, of which three were false - including
|
|
428
|
+
`verify_authenticity_token`, which that controller skips.
|
|
429
|
+
|
|
430
|
+
### Added
|
|
431
|
+
|
|
432
|
+
- **`--no-boot` on `context`, `inspect`, `facts`, `preset`, `watch` and
|
|
433
|
+
`init`.** The static tier was reachable from `tool` and `serve` only.
|
|
434
|
+
- **`--no-boot` reads a repo that has source but no `config/`.** An engine
|
|
435
|
+
keeps its dummy app under `spec/dummy`, so the guard against describing an
|
|
436
|
+
empty directory has to measure source, not boot files.
|
|
437
|
+
|
|
438
|
+
### Changed
|
|
439
|
+
|
|
440
|
+
- **The generated initializer has one indent from top to bottom.** The AI
|
|
441
|
+
Tools section was written at the configure body's indent and every section
|
|
442
|
+
after it flush, so the file an app commits changed indent two lines in and
|
|
443
|
+
stayed there.
|
|
444
|
+
|
|
445
|
+
## [5.23.0] - 2026-08-16
|
|
446
|
+
|
|
447
|
+
### Fixed
|
|
448
|
+
|
|
449
|
+
- **`AstCache.parse_string` caches, as its name always said.** Keyed by
|
|
450
|
+
content digest, sharing the store and its eviction; oversize sources
|
|
451
|
+
bypass instead of raising. A controller was parsed up to ten times per
|
|
452
|
+
static run because every extractor received the text and re-parsed it.
|
|
453
|
+
- **`rails_validate` says which checks it skipped.** When the AST parse
|
|
454
|
+
fails, the three rules with no regex twin used to vanish silently and a
|
|
455
|
+
partially-checked file read as clean; the response now names them. The
|
|
456
|
+
tool also gains its first spec file.
|
|
457
|
+
- **A downstream app exception is the app's again.** The middleware's
|
|
458
|
+
rescue used to convert any error from the app behind it - non-MCP
|
|
459
|
+
requests included - into an MCP error frame. `McpEdge.rack_call` now owns
|
|
460
|
+
the whole Rack-shaped request (path match, session scope, dispatch,
|
|
461
|
+
containment) for the middleware and the standalone server, and the
|
|
462
|
+
pass-through branch runs outside the rescue.
|
|
463
|
+
|
|
464
|
+
### Changed
|
|
465
|
+
|
|
466
|
+
- **The cache fingerprint watches what the resolvers read.** Packs,
|
|
467
|
+
engines, `extra_app_paths`, every concern home, the `Gemfile`,
|
|
468
|
+
`config/locales` and `config/environments` now feed the fingerprint, so
|
|
469
|
+
an edit there cannot serve a stale answer that looks fresh.
|
|
470
|
+
- **One change-detection loop.** `ChangeWatch` owns the watch list, the
|
|
471
|
+
Listen wiring, the fingerprint gate and the code reload; `Watcher` and
|
|
472
|
+
`LiveReload` keep only their reactions (regenerate files; refresh caches
|
|
473
|
+
and notify clients) and their own missing-`listen` policy.
|
|
474
|
+
- **The initializer guard and the `tool_mode` line each have one home.**
|
|
475
|
+
`Install::InitializerFile` holds the guard patterns the generator writes
|
|
476
|
+
and the doctor diagnoses; `SelectionRecord.write_tool_mode` replaces the
|
|
477
|
+
rake task's hand-rolled three-branch rewriter, and only rewrites an
|
|
478
|
+
uncommented line, so the generated commented-out default stays a comment.
|
|
479
|
+
- **Split-rule targets derive from the `Install::AiTool` table.** The
|
|
480
|
+
rules serializers read `rules_dir` (and the AGENTS.md pair) from the
|
|
481
|
+
table doctor and cleanup already read, so a moved path changes every
|
|
482
|
+
surface at once.
|
|
483
|
+
- **The stack helper writes through `SafeFile.atomic_write`** instead of a
|
|
484
|
+
private copy of it, and its app-tree scans take a root, so they are
|
|
485
|
+
covered by specs for the first time.
|
|
486
|
+
- **Every serializer is proven against a context real introspectors
|
|
487
|
+
produced** (`IntrospectedFixture`), so no serializer can read a shape
|
|
488
|
+
production does not make - the class of defect that kept the engines and
|
|
489
|
+
turbo sections dead while hand-built fixtures stayed green.
|
|
490
|
+
|
|
491
|
+
## [5.22.0] - 2026-08-16
|
|
492
|
+
|
|
493
|
+
### Fixed
|
|
494
|
+
|
|
495
|
+
- **A class nested inside a controller, mailer, model or helper file no
|
|
496
|
+
longer contributes its methods to the host's interface.** "Which public
|
|
497
|
+
methods are this class's own" had five transcriptions with five filter
|
|
498
|
+
sets; they now share `ActionResolver`, which filters listener output by the
|
|
499
|
+
owner field the listener always emitted, walks app-owned ancestors, and
|
|
500
|
+
falls back to reflection with the #136 base subtraction. Mailers had kept
|
|
501
|
+
the pre-#136 reflection answer, so a public helper on a non-abstract
|
|
502
|
+
`ApplicationMailer` arrived as a deliverable action; they subtract now, and
|
|
503
|
+
static mailer actions apply the same underscore rule as controllers. (#149)
|
|
504
|
+
|
|
505
|
+
- **Every generated context file states each fact through one renderer.**
|
|
506
|
+
`SectionFacts` owns the models line, the database line, the associations
|
|
507
|
+
list (eight copies, three of them in tools) and the warnings section -
|
|
508
|
+
which now renders in AGENTS.md and .github/copilot-instructions.md too, so
|
|
509
|
+
a half-failed run cannot look clean in two of four files. AGENTS.md stops
|
|
510
|
+
shadowing the shared architecture and footer renderers with poorer copies,
|
|
511
|
+
and its controller list goes through the shared renderer with named
|
|
512
|
+
actions as a depth choice rather than a fork. `Payload` is the reading
|
|
513
|
+
side of the introspection hash: list readers whose key pairs a spec pins
|
|
514
|
+
against the producing introspector's real output, so the next renamed key
|
|
515
|
+
fails a test instead of silently emptying a section. (#151)
|
|
516
|
+
- **API-only apps are told "does not apply", not "not found".**
|
|
517
|
+
`rails_get_helper_methods` and `rails_get_frontend_stack` now consult the
|
|
518
|
+
api-only note like the other eleven call sites. The two hand-rolled
|
|
519
|
+
paginations (`rails_get_schema`, `rails_get_stimulus`) go through the
|
|
520
|
+
shared `paginate`, one offset/limit behavior across all fourteen paginated
|
|
521
|
+
surfaces. (#152)
|
|
522
|
+
- **The interactive install is one program with three voices.** The
|
|
523
|
+
generator, the standalone binary and the rake task each held a full copy
|
|
524
|
+
of the prompts, the removed-tool cleanup, the gitignore append and the
|
|
525
|
+
MCP-config write - about 470 lines that had already drifted: two labels
|
|
526
|
+
for one mode, a menu with file lists in two entries and bare names in the
|
|
527
|
+
third, a byte-identical gitignore block three times. `Install::Program`
|
|
528
|
+
now owns the steps and the wording; each entry supplies its say/ask
|
|
529
|
+
surface and keeps only its closing instructions. The rake menu shows each
|
|
530
|
+
tool's files, and every entry uses one mode label.
|
|
531
|
+
- **Configuration loads the same way on every entry point.** The engine now
|
|
532
|
+
runs `auto_load!` at boot, so `.rails-ai-context.yml` works on the rake
|
|
533
|
+
tasks, the middleware and the engine controller the way the docs always
|
|
534
|
+
said - initializer first, the loader steps aside when a configure block
|
|
535
|
+
ran. `Configuration#ai_tools` answers from `SelectionRecord` when nothing
|
|
536
|
+
set it, so `rails ai:context` stops re-asking a question the record
|
|
537
|
+
already answers, and the recorded `tool_mode` reaches in-app surfaces
|
|
538
|
+
through the record's new reader. A bad YAML value now warns and keeps the
|
|
539
|
+
default the way bad syntax always did, instead of dying raw out of the
|
|
540
|
+
CLI. (#147, #148)
|
|
541
|
+
- **The three static schema sources follow one set of conventions, and every
|
|
542
|
+
schema question can be asked of any of them.** `SchemaReader.for(root)`
|
|
543
|
+
chooses schema.rb, structure.sql or migration replay behind one
|
|
544
|
+
question-level seam, so introspectors asking "does this table carry this
|
|
545
|
+
column" now get answers on structure.sql and migrations-only apps instead
|
|
546
|
+
of nothing. The migration replay is repaired: a replayed `create_table`
|
|
547
|
+
seeds its implicit `id`, `change_column_null` applies in both directions
|
|
548
|
+
(the listener now captures the positional boolean), and `t.references` is
|
|
549
|
+
typed per adapter instead of hardcoded bigint. The conventions live in
|
|
550
|
+
`SchemaConventions`, shared by all three sources the way #140's
|
|
551
|
+
foreign-key fix pioneered; the SQL parser and the replay engine moved to
|
|
552
|
+
`StructureSqlReader` and `MigrationReplay`, and the schema introspector
|
|
553
|
+
halves in size, keeping only presentation. (#150)
|
|
554
|
+
- **Every surface answers "which concerns count" and "how many routes" the
|
|
555
|
+
same way.** Five renderers carried five concern filter sets:
|
|
556
|
+
`rails_get_callbacks` hid every namespaced concern unless it started with
|
|
557
|
+
"App", `rails_analyze_feature` hid all of them, and two ignored the
|
|
558
|
+
`excluded_concerns` config. `ConcernMembership` now decides the payload
|
|
559
|
+
sense once, at the introspector seam, for both tiers - the static
|
|
560
|
+
controller answer goes through `MixinsListener`, so `prepend` counts and a
|
|
561
|
+
singleton-class `include` does not, exactly as booted. Route populations
|
|
562
|
+
(app vs framework split, PUT/PATCH dedup) moved beside the coverage suffix
|
|
563
|
+
into `RouteCoverage`, replacing four hand-kept computations, one of them a
|
|
564
|
+
wholesale copy. CLAUDE.md's per-model concern list now names the concerns
|
|
565
|
+
the app defines, namespaced ones included.
|
|
566
|
+
- **Mounted engines render again in `rails_onboard` and the context files.**
|
|
567
|
+
`EngineIntrospector` emits `:mounted_engines`; the tool read `:engines` then
|
|
568
|
+
`:mounted`, and the stack line read `:mounted`, so both fell back to "none"
|
|
569
|
+
forever. Both now read the introspector's keys and the element's real
|
|
570
|
+
`engine:`/`path:` shape, and the spec fixtures use that shape instead of one
|
|
571
|
+
production never produced. (#144)
|
|
572
|
+
- **The Hotwire stack line and `rails_onboard`'s Real-Time and Frontend
|
|
573
|
+
sections read turbo keys that exist.** `turbo[:frames]`, `[:streams]` and
|
|
574
|
+
`[:broadcasts]` were never emitted (`:turbo_frames`, `:turbo_streams` and
|
|
575
|
+
`:model_broadcasts` are), so the line was dead everywhere and Real-Time
|
|
576
|
+
always fell through to its view-scan fallback. (#145)
|
|
577
|
+
- **`rails_get_context` no longer offers a Related Services section it could
|
|
578
|
+
never fill.** The `:services` key it read has no introspector behind it; the
|
|
579
|
+
dead branch is gone. (#146)
|
|
580
|
+
- **`rails ai:context` stops re-prompting a YAML-configured app on every
|
|
581
|
+
run.** The rake path now loads `.rails-ai-context.yml` the way the
|
|
582
|
+
standalone binary always did; the initializer still wins when its configure
|
|
583
|
+
block ran. (#147)
|
|
584
|
+
- **The standalone binary's `--format` help names opencode and codex.** A
|
|
585
|
+
guard spec pins the help text to `Install::AiTool`, which the Thor class
|
|
586
|
+
body cannot ask at definition time. (#153)
|
|
587
|
+
- **`rails ai:serve` and `rails ai:serve_http` boot under the same timeout
|
|
588
|
+
and rescue as the binary.** A hanging initializer used to hang the task
|
|
589
|
+
forever where `rails-ai-context serve` gives up at 60s; both tasks now boot
|
|
590
|
+
through `BootManager.guard`, and `RAILS_AI_CONTEXT_BOOT_TIMEOUT` is parsed
|
|
591
|
+
in one place. (#154)
|
|
592
|
+
- **doctor's tool list and `rails_search_code`'s exclusion list derive from
|
|
593
|
+
`Install::AiTool`.** Both were hand-typed restatements sitting below the
|
|
594
|
+
ownership spec's detection floor; the search exclusions now also cover the
|
|
595
|
+
generated `app/models/AGENTS.md` pair they previously missed. (#155)
|
|
596
|
+
- **An e2e example asserts `call` and `static_call` answer the same keys.**
|
|
597
|
+
The declaration contract was already enforced (ADR-0002); the shape contract
|
|
598
|
+
was not, so a key added to the booted tier could silently vanish from the
|
|
599
|
+
static one. (#156)
|
|
600
|
+
|
|
8
601
|
## [5.21.3] - 2026-08-12
|
|
9
602
|
|
|
10
603
|
### Fixed
|