rails-ai-context 5.36.7 → 5.36.9
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 +220 -0
- data/README.md +6 -0
- data/docs/CLI.md +1 -1
- data/docs/COMPATIBILITY.md +19 -0
- data/docs/CONFIGURATION.md +1 -1
- data/docs/GUIDE.md +1 -1
- data/docs/PROMPTS.md +3 -3
- data/docs/SECURITY.md +37 -2
- data/docs/SETUP.md +191 -0
- data/docs/STANDALONE.md +2 -2
- data/docs/TOOLS.md +5 -5
- data/docs/TROUBLESHOOTING.md +6 -2
- data/exe/rails-ai-context +53 -20
- data/lib/generators/rails_ai_context/install/install_generator.rb +8 -4
- data/lib/rails_ai_context/boot_manager.rb +7 -1
- data/lib/rails_ai_context/claude_code_settings.rb +1006 -0
- data/lib/rails_ai_context/cli/app_root.rb +126 -16
- data/lib/rails_ai_context/cli/entry_boot.rb +60 -6
- data/lib/rails_ai_context/configuration.rb +116 -34
- data/lib/rails_ai_context/doctor.rb +482 -24
- data/lib/rails_ai_context/gem_lock.rb +14 -7
- data/lib/rails_ai_context/initializer_settings.rb +603 -0
- data/lib/rails_ai_context/install/claude_plugin_removal.rb +107 -0
- data/lib/rails_ai_context/install/program.rb +133 -6
- data/lib/rails_ai_context/install/selection_record.rb +11 -5
- data/lib/rails_ai_context/introspectors/env_references.rb +1 -1
- data/lib/rails_ai_context/introspectors/gemfile_gems.rb +2 -2
- data/lib/rails_ai_context/mcp_config_generator.rb +18 -6
- data/lib/rails_ai_context/redaction.rb +749 -26
- data/lib/rails_ai_context/resources.rb +15 -0
- data/lib/rails_ai_context/safe_path.rb +101 -11
- data/lib/rails_ai_context/secret_columns.rb +1 -1
- data/lib/rails_ai_context/server.rb +6 -1
- data/lib/rails_ai_context/sql_gate.rb +5 -0
- data/lib/rails_ai_context/tools/get_env.rb +20 -30
- data/lib/rails_ai_context/tools/get_partial_interface.rb +12 -3
- data/lib/rails_ai_context/tools/get_view.rb +6 -3
- data/lib/rails_ai_context/tools/review_changes.rb +74 -29
- data/lib/rails_ai_context/tools/safe_call.rb +5 -0
- data/lib/rails_ai_context/tools/search_code.rb +24 -8
- data/lib/rails_ai_context/tools/search_docs.rb +2 -1
- data/lib/rails_ai_context/update_check.rb +17 -4
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context.rb +26 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4f5530fd8b0a92932d1810f018d01de51af456064f92de2f9b95749360e43a5
|
|
4
|
+
data.tar.gz: dd8098d2f6d049088e92d857a407a69e84a19963379eaa698749bab6d5564539
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5ac4f8fb6bdd15d56bf3db2aec1b5e53d3ca39f437d2acead40769014f3747546a931c2ad531250c614ff3f418eaae5d82bf84fe2790036e7931c50f515dd12b
|
|
7
|
+
data.tar.gz: 943060825f72f4e118bebc8160a209787582d8223050ca65cbdd6149bff4819fc12966962dc2ceb1f999689571db1121f908b68bb4ca27563094d7cf1874b6fd
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,226 @@ 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.36.9] - 2026-10-23
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
- **`rails_read_logs` and `rails_query` hide a value under a plural secret name
|
|
13
|
+
or a list of codes** (#606). A secret word ending a name in its plural (`api_keys`,
|
|
14
|
+
`tokens`, `secrets`, `passwords`, `accessTokens`) named no secret, and
|
|
15
|
+
neither did `recovery_codes`, `backup_codes`, `recoveryCodes` or
|
|
16
|
+
`backupCodes`, so their values came back from a log line, inline or bound,
|
|
17
|
+
and `rails_query` returned such a column. A list or hash logged under a
|
|
18
|
+
secret name is now filtered whole, on one line or spread over lines, where
|
|
19
|
+
only its first element was, and so are a query parameter with a secret word
|
|
20
|
+
anywhere in its bracketed name (`user[password][new]=`) and every value of
|
|
21
|
+
`API_KEYS=a,b`. In a log line a count stays shown (`tokens_used`,
|
|
22
|
+
`token_count`, `prompt_tokens: 12`, `inputTokens: 12`), and so do `keys`,
|
|
23
|
+
`primary_keys` and `cache_keys`; `rails_query` still refuses a column whose
|
|
24
|
+
name holds `token` unless `query_allowed_columns` lists it.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **The repository is a Claude Code plugin marketplace.** `claude plugin install
|
|
29
|
+
rails-ai-context --marketplace crisnahine/rails-ai-context --scope project`
|
|
30
|
+
adds the MCP server and gives each session in a Rails app the six rules the
|
|
31
|
+
context files open with (unless the app's own files carry them or its config
|
|
32
|
+
turns them off), and writes no context files or MCP config. It runs the
|
|
33
|
+
installed rails-ai-context, for the `ruby` on `PATH`, Windows included.
|
|
34
|
+
Where it cannot serve (no gem for that Ruby, an older one, a Ruby too old,
|
|
35
|
+
several apps or none) its server still connects, with no tools, and tells
|
|
36
|
+
the session why, naming the Ruby and the folder it ran in, since Claude Code
|
|
37
|
+
skips a plugin server that failed to start in every new session for 15
|
|
38
|
+
minutes. Its command line names no versioned folder, so one
|
|
39
|
+
`allowedMcpServers` entry admits it across updates. See
|
|
40
|
+
[SETUP.md](docs/SETUP.md#as-a-plugin).
|
|
41
|
+
- **`doctor` knows the plugin.** It reads Claude Code's settings, install
|
|
42
|
+
registry, `/mcp` toggles and MCP policy per session folder, as Claude Code
|
|
43
|
+
does. Where the plugin serves the app it counts as Claude Code's MCP config;
|
|
44
|
+
where it does not run, `doctor` says why (not installed, a local marketplace
|
|
45
|
+
folder that is gone, turned off in `/mcp`, blocked by the MCP or marketplace
|
|
46
|
+
policy, naming each rule and the file that holds it, or a managed settings
|
|
47
|
+
file Claude Code cannot read) instead of sending you to `init`. A
|
|
48
|
+
`.mcp.json`, user scope or local scope server of the gem's, an HTTP one
|
|
49
|
+
included, that a session would start beside it is a warning naming the
|
|
50
|
+
commands that remove it, since Claude Code would list every tool twice. Where
|
|
51
|
+
Claude Code never ran, as on CI, what the plugin needs is a warning, so a
|
|
52
|
+
STRICT run stays green. Where an organization deploys `managed-mcp.json`,
|
|
53
|
+
which starts its own servers alone, `doctor` judges Claude Code by it; it
|
|
54
|
+
follows a `CLAUDE_CONFIG_DIR` set in the settings' `env` block, and warns
|
|
55
|
+
about one there or in the environment that is not an absolute path, which no
|
|
56
|
+
one can read from outside a session.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- **The install writes no Claude Code server where the app's committed settings
|
|
61
|
+
turn the plugin on.** `init`, the install generator and `context` leave
|
|
62
|
+
`.mcp.json` alone there and say why, so doctor's fix to remove an old entry
|
|
63
|
+
lasts; `init` in a folder holding one app does the same where that folder's
|
|
64
|
+
committed settings turn the plugin on, and writes the app's own `.mcp.json`
|
|
65
|
+
for sessions started in the app. Its summary marks the plugin off where it
|
|
66
|
+
does not start for you. Where only your own settings turn the plugin on,
|
|
67
|
+
Claude Code's next step says to turn the `.mcp.json` server off with `/mcp`.
|
|
68
|
+
- **The update notice in a plugin's server says to reconnect it.** It named
|
|
69
|
+
`init`, which would add a second server beside the plugin's.
|
|
70
|
+
- **`uninstall` names the Claude Code plugin** that runs the installed gem, and
|
|
71
|
+
the commands that turn it off for the app (one per folder whose local
|
|
72
|
+
settings its sessions read) and take it out, before it suggests removing the
|
|
73
|
+
gem.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- **A YAML alias in `.rails-ai-context.yml` no longer stops the app's boot.**
|
|
78
|
+
The loader refused aliases and the error escaped the engine's initializer.
|
|
79
|
+
Aliases now apply, and YAML that will not load warns and keeps the defaults.
|
|
80
|
+
So does a value of the wrong shape, such as a nested list for `ai_tools`, a
|
|
81
|
+
list for `preset` or a mapping for a number, which raised past the
|
|
82
|
+
initializer too, in one short line however many aliases the file nests. A
|
|
83
|
+
number written as text reads as that number: `1e5` had read as 1, a
|
|
84
|
+
`query_timeout` of `010` as 8, and a quoted number failed the tools that
|
|
85
|
+
compare it.
|
|
86
|
+
- **`rails-ai-context init` and `context` read an odd config file as the
|
|
87
|
+
loader does** (#520). A file holding a list, not settings, and a `tool_mode`
|
|
88
|
+
that is not a name stopped both with a Ruby error. They now warn and keep the
|
|
89
|
+
default, as the loader does.
|
|
90
|
+
- **A number setting from a configure block is checked when it is set.** Text
|
|
91
|
+
such as an ENV lookup, a negative count or a list was stored as given for the
|
|
92
|
+
settings with no writer of their own, and a tool failed on it later. It now
|
|
93
|
+
reads as the number it is written as, and a wrong value raises ArgumentError
|
|
94
|
+
at boot, as `cache_ttl` already did; from the YAML file it warns and keeps
|
|
95
|
+
the default. `live_reload_debounce` likewise takes a finite number of
|
|
96
|
+
seconds, zero or more.
|
|
97
|
+
- **A query log tag in a statement's trailing comment is read as a tag.**
|
|
98
|
+
`rails_read_logs` read the comment as SQL, so where the statement named a
|
|
99
|
+
secret column, the first tag's value (`/*application='App'*/`) was
|
|
100
|
+
filtered. A tag is now filtered only when its key names a secret, in the
|
|
101
|
+
`key='value'` and `key:value` forms; MySQL's `/*!` and `/*+` stay SQL.
|
|
102
|
+
- **`live_reload: "false"` in `.rails-ai-context.yml` no longer turns live
|
|
103
|
+
reload on.** Any name was taken and meant auto. `true` and `false`, as
|
|
104
|
+
booleans or text, and `auto` now count; anything else warns and keeps auto
|
|
105
|
+
from the file, and raises ArgumentError from a configure block.
|
|
106
|
+
- **A Ruby file the app cannot read no longer stops the static tier.** Since
|
|
107
|
+
5.36.8 the static tier reads the security settings an app's Ruby config
|
|
108
|
+
writes, and one file it could not open made every answer an error; no boot
|
|
109
|
+
loads that file either, so it now names no setting.
|
|
110
|
+
- **A `BUNDLE_GEMFILE` inherited from another project is named.** The app
|
|
111
|
+
booted against that bundle and failed with a bare LoadError; the warning
|
|
112
|
+
bundle exec already had now covers the variable on its own.
|
|
113
|
+
- **A running server survives `gem cleanup`.** The gem loads its files as it
|
|
114
|
+
needs them, so a tool call after its copy was removed raised a LoadError that
|
|
115
|
+
ended the server. A call that needs a file it has not loaded yet now answers
|
|
116
|
+
with an error that says to reconnect the server, or over HTTP to restart the
|
|
117
|
+
process serving it.
|
|
118
|
+
- **A boot failure names the Ruby the app pins** when another one runs and the
|
|
119
|
+
failure can come from that, a missing gem or a load error, as when a client
|
|
120
|
+
starts in a monorepo's root that pins its own.
|
|
121
|
+
- **No update notice where the app's lockfile names the version running.**
|
|
122
|
+
That lockfile decides the copy that serves the app, so `gem update` changed
|
|
123
|
+
nothing there.
|
|
124
|
+
- **A lockfile rewritten within the clock's resolution is read again.** The
|
|
125
|
+
app's bundle was cached by modification time alone, so a second write in the
|
|
126
|
+
same tick kept the first answer.
|
|
127
|
+
- **An app whose bundle has its own path runs its own copy.** Where `bundle
|
|
128
|
+
config set path`, a deployment bundle or `BUNDLE_PATH` put the gem under
|
|
129
|
+
the app, the installed copy served it and the app's boot loaded the
|
|
130
|
+
bundle's copy over it, two copies in one process, whichever version the
|
|
131
|
+
bundle locked. The bundle's path is now read as Bundler reads its settings.
|
|
132
|
+
|
|
133
|
+
## [5.36.8] - 2026-10-23
|
|
134
|
+
|
|
135
|
+
### Security
|
|
136
|
+
|
|
137
|
+
- **The static tier keeps the security settings the app's Ruby config writes.**
|
|
138
|
+
No initializer runs without a boot, so on the static tier (`--no-boot`, or a
|
|
139
|
+
boot that failed) a `sensitive_patterns`, `excluded_paths` or `skip_tools`
|
|
140
|
+
value set in a `RailsAiContext.configure` block was dropped, and a file the
|
|
141
|
+
block made sensitive was searched and read. The static tier now reads every
|
|
142
|
+
Ruby file of the app that names the gem, without running it: a file under
|
|
143
|
+
`config/`, and the top level of any other, whole, and a method's body
|
|
144
|
+
elsewhere (a tool's, a helper's) for what it writes, with tests, installed
|
|
145
|
+
gems and generator templates left out. The literal values those files write
|
|
146
|
+
to the three keys are added to the YAML's; a removal is left out, the
|
|
147
|
+
stricter reading. A value it cannot read, such as one built from a constant
|
|
148
|
+
or the environment, a pattern changed in place, or the configuration reached
|
|
149
|
+
through `send`, stops the static tier with an error that names the file and
|
|
150
|
+
line.
|
|
151
|
+
- **A sensitive pattern covers its file wherever it sits.**
|
|
152
|
+
`config/database.yml` and the other patterns with a `/` matched only at the
|
|
153
|
+
app root, so an engine's, a pack's or a test/dummy app's copy was read by
|
|
154
|
+
every file tool, and `**/id_rsa` missed an `id_rsa` at the root. A pattern
|
|
155
|
+
now reads the way a `.gitignore` line does: one with a `/` names its file
|
|
156
|
+
below the root too, a leading `/` anchors it at the root, a trailing `/`
|
|
157
|
+
names a folder and everything in it, and `**/` stands for any number of
|
|
158
|
+
folders, none included.
|
|
159
|
+
A file a link brings in is also asked by its name in the app's repository, so
|
|
160
|
+
a link to a pack's `config/` folder cannot hide the `config/` a pattern looks
|
|
161
|
+
for. `doctor`'s gitignore check reads the files below the root the same way.
|
|
162
|
+
A path a caller names that is longer than 4096 bytes, which no file system
|
|
163
|
+
holds, is answered as not found before any pattern reads it.
|
|
164
|
+
- **`rails_search_code` reads a path the way the file system does.** A path
|
|
165
|
+
with a `.` segment (`./`, `config/.`) made ripgrep print `./config/...`, and a
|
|
166
|
+
link inside the app to `config/` made it print the link's name, so no
|
|
167
|
+
sensitive pattern matched and a sensitive file's lines came back. The path is
|
|
168
|
+
now cleaned, and one inside the app is searched as the folder it leads to.
|
|
169
|
+
- **`rails_search_code` and `rails_review_changes` keep a private key's body
|
|
170
|
+
hidden on every line they show.** A search row is redacted with its whole
|
|
171
|
+
file, but ripgrep searched files over `max_file_size`, which the redaction
|
|
172
|
+
does not read, so each body line of a PEM key in such a file came back on its
|
|
173
|
+
own, unfiltered. ripgrep now skips those files as the Ruby search does, and a
|
|
174
|
+
file named as the path over the limit answers "File too large". A diff hunk
|
|
175
|
+
that opened inside a key showed its body lines the same way; each diff line
|
|
176
|
+
is now redacted with the whole side of the file it comes from, the hunk
|
|
177
|
+
header's line too. The diff is read with the repository's colour, external
|
|
178
|
+
diff and textconv settings off, and a diff over `max_file_size` or a merge
|
|
179
|
+
conflict's combined diff shows no lines. A submodule's change shows as the
|
|
180
|
+
commits it moves between, where `diff.submodule=diff` printed its files'
|
|
181
|
+
lines unredacted. A changed file that is not UTF-8 is read, where the tool
|
|
182
|
+
stopped with an encoding error.
|
|
183
|
+
- **`rails_read_logs` filters a secret column's value written inline in a
|
|
184
|
+
statement.** MySQL and MariaDB run no prepared statements, so Active Record
|
|
185
|
+
logs every value inline with backquoted names, and `insert_all` and
|
|
186
|
+
`upsert_all` write their rows inline on every adapter. Password hashes,
|
|
187
|
+
tokens and reset keys came back from INSERT, UPDATE and WHERE lines, from the
|
|
188
|
+
`CAST()` and `LOWER()` a uniqueness validation writes, and from the database
|
|
189
|
+
errors that repeat a row's values. A value compared with a secret column
|
|
190
|
+
(through a JSON path too), listed for it with `IN` or `BETWEEN`, inserted
|
|
191
|
+
under it, or bound to its placeholder is now filtered whatever quotes the
|
|
192
|
+
names and the value carry, and so is a value whose column cannot be told in a
|
|
193
|
+
statement that names a secret column, every value of an `INSERT` with no
|
|
194
|
+
column list, and a secret-named key in JSON a value holds. PostgreSQL's
|
|
195
|
+
failing row is filtered whole, as it names no column, MySQL's duplicate entry
|
|
196
|
+
unless Rails named the index after columns that hold no secret, PostgreSQL's
|
|
197
|
+
duplicate key when the key names a secret, and the statement excerpt a MySQL
|
|
198
|
+
syntax error quotes; a message cut off is filtered to the end of its line. A
|
|
199
|
+
JSON log line's strings are read the same way, a value under a secret's key
|
|
200
|
+
and a JSON logger's binds included.
|
|
201
|
+
- **`rails_get_config(section: "env")` hides a DSN or a webhook URL wherever it
|
|
202
|
+
is set.** A Dockerfile `ENV` or `ARG` value, an `.env.example` value and a
|
|
203
|
+
default in code went through the generic redaction only, which reads a URL's
|
|
204
|
+
password but not a key before `@` or a token in the path, so a Sentry DSN or
|
|
205
|
+
a Slack webhook printed in full where the Kamal section hid the same value.
|
|
206
|
+
Those values now read `[FILTERED]` when their shape can carry a secret: a URL
|
|
207
|
+
whose user part is a key or a password, a webhook's address, a token in a
|
|
208
|
+
query, a key-like run of letters and digits, and a `BUNDLE_<HOST>` gem server
|
|
209
|
+
credential. Paths, hosts and local or public URLs still show. An
|
|
210
|
+
`.env.example` value in quotes keeps a `#`, where the part after it printed
|
|
211
|
+
as a comment, and an inline comment is hidden when it holds a secret's shape.
|
|
212
|
+
- **`rails_get_view` and `rails_get_partial_interface` match a path as plain
|
|
213
|
+
text.** A missing view's folder and a partial's name were globbed as written,
|
|
214
|
+
so each `{a,b}` group doubled the work for every view file, and one crafted
|
|
215
|
+
path held a server thread for minutes.
|
|
216
|
+
- **`rails_query` refuses a `PROCEDURE` clause on MySQL and MariaDB.**
|
|
217
|
+
MariaDB's `SELECT ... PROCEDURE ANALYSE()` returns each column's lowest and
|
|
218
|
+
highest values, lengths and null counts, a secret column's included, under
|
|
219
|
+
names of its own that the result redaction does not read. `SHOW PROCEDURE
|
|
220
|
+
STATUS` still runs.
|
|
221
|
+
- **The patterns that find a URL's password, a JWT and an `sk-` key read a
|
|
222
|
+
line in time that grows with its length.** The URL pattern tried a scheme from
|
|
223
|
+
each letter of a run, and the JWT and `sk-` patterns read to a run's end from
|
|
224
|
+
every repeat in it, so a 100 KB log line or env value took 15 to 35 seconds. A
|
|
225
|
+
URL's password is now read from its `://`, after a shell default's `:-` too,
|
|
226
|
+
and a JWT and an `sk-` key are read so that a run of repeats costs its length.
|
|
227
|
+
|
|
8
228
|
## [5.36.7] - 2026-10-22
|
|
9
229
|
|
|
10
230
|
### Security
|
data/README.md
CHANGED
|
@@ -81,6 +81,12 @@ gem install rails-ai-context
|
|
|
81
81
|
rails-ai-context init # or: rails-ai-context init --tools claude --defaults
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
As a Claude Code plugin, which adds the MCP server and gives each session the six rules below, and writes no context files or MCP config. It runs the installed gem, so install that first, with `gem install rails-ai-context` (or `bundle add` alone, when the bundle installs where your `ruby` looks). Run it in the app's folder, and commit the `.claude/settings.json` it writes; the install generator and `init` then leave Claude Code's `.mcp.json` to the plugin ([details](docs/SETUP.md#as-a-plugin)):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
claude plugin install rails-ai-context --marketplace crisnahine/rails-ai-context --scope project
|
|
88
|
+
```
|
|
89
|
+
|
|
84
90
|
`--tools` takes `claude`, `cursor`, `copilot`, `opencode`, `codex` or `all`. Add `--cli` for context files only, or `--mcp-only` for the MCP config only. Re-running is safe: with `--defaults` it keeps your recorded tools and setup, keeps what you have and adds what is missing, and in a `CLAUDE.md` you wrote it only touches the block between its `<!-- BEGIN/END rails-ai-context -->` markers. Every step is in [What the install generator does](docs/GUIDE.md#what-the-install-generator-does).
|
|
85
91
|
|
|
86
92
|
Needs Ruby 3.1+ and Rails 7.0+, on Linux, macOS or Windows. Optional: `brakeman` for `security_scan`, `listen` for `watch`, `ripgrep` for faster `search_code`.
|
data/docs/CLI.md
CHANGED
|
@@ -320,7 +320,7 @@ rails-ai-context uninstall
|
|
|
320
320
|
bundle exec rails-ai-context uninstall # in-Gemfile; then bundle remove rails-ai-context
|
|
321
321
|
```
|
|
322
322
|
|
|
323
|
-
What you wrote stays: your lines around the markers in a shared `CLAUDE.md` or `AGENTS.md`, your rule files, other servers in an MCP config, and a pre-commit hook you changed by hand (named, not touched). Its own config files go even when you edited them: `.rails-ai-context.yml` and the initializer configure nothing once the gem is gone. An HTTP entry under the gem's name goes too, since only the gem's endpoint answers it. A `mount RailsAiContext::Engine` line in `config/routes.rb` is named with its line number for you to delete. In a folder of apps it uninstalls every app below and the folder's MCP configs. It needs no boot. An entry you added by hand to another client's own config from `mcp-entry` (Claude Desktop and the like) lives outside the app, so remove it there. Its last lines say how to remove the gem itself: `bundle remove rails-ai-context` when the Gemfile lists it, `gem uninstall rails-ai-context` once no other app uses the installed copy. Details in the [Guide](GUIDE.md#uninstalling).
|
|
323
|
+
What you wrote stays: your lines around the markers in a shared `CLAUDE.md` or `AGENTS.md`, your rule files, other servers in an MCP config, and a pre-commit hook you changed by hand (named, not touched). Its own config files go even when you edited them: `.rails-ai-context.yml` and the initializer configure nothing once the gem is gone. An HTTP entry under the gem's name goes too, since only the gem's endpoint answers it. A `mount RailsAiContext::Engine` line in `config/routes.rb` is named with its line number for you to delete. In a folder of apps it uninstalls every app below and the folder's MCP configs. It needs no boot. An entry you added by hand to another client's own config from `mcp-entry` (Claude Desktop and the like) lives outside the app, so remove it there. Where the Claude Code plugin runs the gem, it names the commands that turn the plugin off and take it out ([SETUP](SETUP.md#as-a-plugin)), since the plugin's server fails once the gem is gone. Its last lines say how to remove the gem itself: `bundle remove rails-ai-context` when the Gemfile lists it, `gem uninstall rails-ai-context` once no other app uses the installed copy. Details in the [Guide](GUIDE.md#uninstalling).
|
|
324
324
|
|
|
325
325
|
### `mcp-entry`
|
|
326
326
|
|
data/docs/COMPATIBILITY.md
CHANGED
|
@@ -366,6 +366,25 @@ Proof sources:
|
|
|
366
366
|
security, gate, redaction and database specs on Rails 7.0 (Ruby 3.1), 7.1,
|
|
367
367
|
7.2 and 8.1 against live PostgreSQL; and the unit suite and the E2E harness
|
|
368
368
|
on Linux, macOS and Windows.
|
|
369
|
+
In the v5.36.8 release QA: each fix's example failing on the code before
|
|
370
|
+
it; `rails_read_logs` against Active Record 8.0's own logs from live
|
|
371
|
+
PostgreSQL 16, MySQL 8.4 and MariaDB 13 for inline values, binds and the
|
|
372
|
+
errors that repeat a row, and against a JSON logger's lines; the static-tier
|
|
373
|
+
settings reader, `rails_search_code`, `rails_get_config` and
|
|
374
|
+
`rails_review_changes` on five open-source apps and two private ones with
|
|
375
|
+
`--no-boot`, compared with v5.36.7 and every changed answer checked; the
|
|
376
|
+
touched specs on Rails 7.0 (Ruby 3.1) and on prism 1.4; and the unit suite
|
|
377
|
+
and the E2E harness on Linux, macOS and Windows.
|
|
378
|
+
In the v5.36.9 release QA: each fix's example failing on the code before
|
|
379
|
+
it; `doctor`'s plugin checks against what Claude Code 2.1.308 and 2.1.309
|
|
380
|
+
do with the same settings (marketplaces, enabled plugins, server command
|
|
381
|
+
and URL rules, managed files and `managed-mcp.json`), and the plugin's
|
|
382
|
+
launcher under Ruby 2.6 and 3.1; `rails_read_logs` and `rails_query` on
|
|
383
|
+
plural secret names and code lists, compared with v5.36.8 in a booted
|
|
384
|
+
SQLite app; the QA ladder on the SQLite labs in every tier, the E2E harness
|
|
385
|
+
and one large open-source app, booted; the unit suite on Ruby 3.4 and on
|
|
386
|
+
Rails 7.0 (Ruby 3.1); and the unit suite and the E2E harness on Linux,
|
|
387
|
+
macOS and Windows.
|
|
369
388
|
2. Non-crash coverage for every built-in tool including `get_view` in
|
|
370
389
|
`spec/e2e/in_gemfile_install_spec.rb`'s full-tool sweep; output correctness
|
|
371
390
|
(ivar cross-check, render-form detection, partial interfaces) verified
|
data/docs/CONFIGURATION.md
CHANGED
|
@@ -39,7 +39,7 @@ preset: full
|
|
|
39
39
|
### Precedence
|
|
40
40
|
|
|
41
41
|
> [!IMPORTANT]
|
|
42
|
-
> `configure` block > YAML > Defaults, merged key by key. The file is read once, at boot, before `config/initializers`, so an initializer may assign a key or edit it in place (`config.skip_tools << "rails_query"`) and both survive. A block that runs *before* the file - in `config/application.rb` or an environment file - must assign a key to keep it (`config.skip_tools = ["rails_query"]`), because an in-place edit there is replaced when the file loads. A key no block assigns keeps the YAML value. Corrupted YAML degrades gracefully with a warning, and a key the gem does not know warns on stderr and is ignored while the rest of the file applies.
|
|
42
|
+
> `configure` block > YAML > Defaults, merged key by key. The file is read once, at boot, before `config/initializers`, so an initializer may assign a key or edit it in place (`config.skip_tools << "rails_query"`) and both survive. A block that runs *before* the file - in `config/application.rb` or an environment file - must assign a key to keep it (`config.skip_tools = ["rails_query"]`), because an in-place edit there is replaced when the file loads. A key no block assigns keeps the YAML value. Corrupted YAML degrades gracefully with a warning, and a key the gem does not know warns on stderr and is ignored while the rest of the file applies. On the static tier no initializer runs: the literal values that the app's Ruby files naming the gem write to `sensitive_patterns`, `excluded_paths` and `skip_tools` are read without running them and added to the YAML's, and one the gem cannot read stops the static tier with an error naming its file and line ([SECURITY.md](SECURITY.md#configuration)).
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
data/docs/GUIDE.md
CHANGED
|
@@ -159,7 +159,7 @@ rails-ai-context uninstall # standalone; then, once no app uses it
|
|
|
159
159
|
- The lines the install added to `.gitignore`, with their comments.
|
|
160
160
|
- The pre-commit hook, when it is the one the install wrote and covers only this app. A hook that covers other apps of the repository loses this one; a hook changed by hand is named and left for you.
|
|
161
161
|
|
|
162
|
-
A `mount RailsAiContext::Engine` line in `config/routes.rb` is your code, so it is named with its line number, not edited. Run in a folder of apps, it does the same for every app below and for the folder's MCP configs. It needs no boot, so it works on an app that cannot start, and the binary of any install can run it: in an app whose bundle pins a version from before `uninstall` existed, `gem install rails-ai-context` and run `rails-ai-context uninstall`.
|
|
162
|
+
A `mount RailsAiContext::Engine` line in `config/routes.rb` is your code, so it is named with its line number, not edited. Run in a folder of apps, it does the same for every app below and for the folder's MCP configs. It needs no boot, so it works on an app that cannot start, and the binary of any install can run it: in an app whose bundle pins a version from before `uninstall` existed, `gem install rails-ai-context` and run `rails-ai-context uninstall`. Where the Claude Code plugin runs the gem, it first names the commands that turn the plugin off and take it out ([SETUP](SETUP.md#as-a-plugin)); a release from before the plugin names none, so turn it off yourself with `claude plugin disable rails-ai-context@rails-ai-context --scope local`.
|
|
163
163
|
|
|
164
164
|
---
|
|
165
165
|
|
data/docs/PROMPTS.md
CHANGED
|
@@ -276,8 +276,8 @@ rails-ai-context isn't working right in this Rails app. Find out why and fix it.
|
|
|
276
276
|
1. Check the version: `bundle exec rails-ai-context version` if the Gemfile lists rails-ai-context, else `rails-ai-context version`. If it is older than 5.35.0, upgrade first (`bundle update rails-ai-context --conservative`, or `gem install rails-ai-context -v '>= 5.35.0'`) and check the version again: older versions read `--defaults` in step 5 as every AI tool. If the Gemfile pins it below 5.35.0, change its rails-ai-context line to `gem "rails-ai-context", ">= 5.35.0", group: :development` and run the update again.
|
|
277
277
|
2. Run doctor: `bundle exec rails ai:doctor` if the Gemfile lists the gem, else `rails-ai-context doctor`. Read every failing or warning check and do what each one says.
|
|
278
278
|
3. If doctor itself cannot run because the app won't boot, run `bundle exec rails-ai-context tool onboard --no-boot` (or without bundle exec when standalone) to confirm the tools still answer from source, and tell me what stops the boot.
|
|
279
|
-
4. If my MCP tools are missing, open the MCP config file of the AI tool you are (Claude Code .mcp.json, Cursor .cursor/mcp.json, GitHub Copilot .vscode/mcp.json, OpenCode opencode.json, Codex CLI .codex/config.toml) and check it has a rails-ai-context entry. Run that entry's command and args by hand with `--help` added at the end, for example `bundle exec rails-ai-context serve --help`, and check it prints the serve usage.
|
|
280
|
-
5.
|
|
279
|
+
4. If my MCP tools are missing, open the MCP config file of the AI tool you are (Claude Code .mcp.json, Cursor .cursor/mcp.json, GitHub Copilot .vscode/mcp.json, OpenCode opencode.json, Codex CLI .codex/config.toml) and check it has a rails-ai-context entry. Run that entry's command and args by hand with `--help` added at the end, for example `bundle exec rails-ai-context serve --help`, and check it prints the serve usage. With the Claude Code plugin (rails-ai-context@rails-ai-context enabled in .claude/settings.json, .claude/settings.local.json or my user settings, or doctor names the plugin) there is no entry: where its server has no tools, its instructions in your context say why and what to run; where /mcp lists it as failed, ask me to start `claude --debug` and read that server's stderr. `ruby -S gem list rails-ai-context` run here shows the versions the `ruby` Claude Code starts has.
|
|
280
|
+
5. If doctor named a missing or broken MCP config, missing context files, or Codex's saved Ruby paths, repair the install by running it again: `bundle exec rails generate rails_ai_context:install --defaults` if the Gemfile lists the gem, otherwise `rails-ai-context init --defaults`. On a re-run `--defaults` keeps the recorded AI tools and setup, keeps what exists and adds what is missing. For Codex CLI after a Ruby version change, it also refreshes the Ruby paths saved in .codex/config.toml.
|
|
281
281
|
6. Tell me what was wrong, what you changed, and the "Next step" lines the install printed if I need to do one.
|
|
282
282
|
```
|
|
283
283
|
|
|
@@ -288,7 +288,7 @@ Removes everything the gem wrote and nothing you wrote: your part of a shared `C
|
|
|
288
288
|
```text
|
|
289
289
|
Remove rails-ai-context from this Rails app completely. If the rails-ai-context entry in my MCP config runs `docker` (this app's Ruby lives in a container), run steps 1 and 3 inside that container, prefixed the way the entry's args are.
|
|
290
290
|
|
|
291
|
-
1. Run `bundle exec rails-ai-context uninstall` if the Gemfile lists rails-ai-context, else `rails-ai-context uninstall`. If the command is missing or unknown, run `gem install rails-ai-context -v '>= 5.35.0'` and then `rails-ai-context uninstall`.
|
|
291
|
+
1. Run `bundle exec rails-ai-context uninstall` if the Gemfile lists rails-ai-context, else `rails-ai-context uninstall`. If the command is missing or unknown, run `gem install rails-ai-context -v '>= 5.35.0'` and then `rails-ai-context uninstall`. If its output names the Claude Code plugin, run the commands it names for it; if it does not, but rails-ai-context@rails-ai-context is enabled in .claude/settings.json, .claude/settings.local.json or my user settings, run `claude plugin disable rails-ai-context@rails-ai-context --scope local` here.
|
|
292
292
|
2. If its output names a config/routes.rb line that mounts RailsAiContext::Engine, delete that line.
|
|
293
293
|
3. If the Gemfile lists rails-ai-context, run `bundle remove rails-ai-context`.
|
|
294
294
|
4. If .git/info/exclude lists rails-ai-context files (a private setup), delete those lines. If I added a rails-ai-context entry to another MCP client's own config (Claude Desktop, Windsurf, Zed and the like), remove that entry there too.
|
data/docs/SECURITY.md
CHANGED
|
@@ -57,6 +57,7 @@ Before any query reaches the database, the SQL gate (`RailsAiContext::SqlGate`)
|
|
|
57
57
|
- **Blocks lock clauses**: FOR UPDATE, FOR SHARE, FOR NO KEY UPDATE, FOR KEY SHARE and LOCK IN SHARE MODE, with whatever follows them (NOWAIT, SKIP LOCKED, OF, WAIT). On PostgreSQL the clause is read on quote-masked text, so `LIKE '%ready for update%'` is data: its read-only transaction refuses a locking read anyway. MySQL's read-only transaction lets `FOR SHARE` and `LOCK IN SHARE MODE` through, so there and on SQLite the raw text decides
|
|
58
58
|
- **Blocks SHOW forms that tell of other sessions, replication and logs, on MySQL and MariaDB**: `SHOW GRANTS`, `SHOW PROCESSLIST` and `SHOW FULL PROCESSLIST`, `SHOW BINLOG`, `SHOW BINARY LOGS` and `SHOW BINARY LOG STATUS`, `SHOW RELAYLOG EVENTS`, the `SHOW SLAVE` and `SHOW SLAVES` forms, `SHOW MASTER`, the `SHOW REPLICA` and `SHOW REPLICAS` forms, `SHOW ALL SLAVES STATUS` and `SHOW ALL REPLICAS STATUS`, `SHOW ENGINE ... STATUS`, `SHOW PROFILE` and `SHOW PROFILES`, `SHOW OPEN TABLES`, `SHOW EXPLAIN FOR` and `SHOW ANALYZE FOR`, `SHOW USER_STATISTICS`, `SHOW CLIENT_STATISTICS` and the `SHOW WSREP_` forms. The answer is "Blocked: sensitive SHOW command". They are matched on the raw text, so the words inside a string literal are refused too
|
|
59
59
|
- **Blocks `EXPLAIN FOR CONNECTION`, on MySQL and MariaDB**: it shows the plan of the statement another session is running. It is refused also as `DESCRIBE` or `DESC`, with a `FORMAT`, and inside a version comment. The answer is "Blocked: EXPLAIN FOR CONNECTION is not run. It shows the plan of the statement another session is running." It is read on quote-masked text, so a string literal that holds the words runs
|
|
60
|
+
- **Blocks a `PROCEDURE` clause, on MySQL and MariaDB**: MariaDB's `SELECT ... PROCEDURE ANALYSE()` returns each column's lowest and highest values, a secret column's too, under names of its own that the result redaction does not read. `PROCEDURE` is a reserved word there, so a bare one outside a `SHOW` statement is refused in any spelling, and `SHOW PROCEDURE STATUS` runs. The answer starts "Blocked: a PROCEDURE clause is not run."
|
|
60
61
|
- **Blocks SHOW with a WHERE clause**, on every family. MySQL's statement timeout holds `SELECT` only, so a `SHOW ... WHERE` filter would run past `query_timeout`. `SHOW ... LIKE` runs. The answer names the query to use: "Blocked: SHOW with a WHERE clause is not run, because MySQL's statement timeout does not hold it. SHOW ... LIKE runs. For any other filter, SELECT from information_schema, for example `SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()`."
|
|
61
62
|
- **Blocks SELECT INTO**: prevents table creation via SELECT. After a `WITH` clause an `INTO` can belong to a write, and such a statement is answered as that write: `WITH ... INSERT INTO ...` answers "Blocked: contains INSERT", and `REPLACE` and `MERGE` are named the same way. `INTO OUTFILE` / `INTO DUMPFILE` disk writes, and the file and network functions below, stay matched on the **raw (unmasked) text**. No read-only transaction stops a server-side file write, so `INTO OUTFILE` inside a string literal is refused on every database family, and a file function inside one on the family that has it
|
|
62
63
|
- **Blocks file and network functions**, each on the database family that has it. PostgreSQL: `pg_read_file`, `pg_read_binary_file`, `pg_ls_dir`, `pg_stat_file`, `lo_import`, `lo_export`, `dblink*` and their relatives. MySQL and MariaDB: `LOAD DATA` and `LOAD_FILE`. SQLite: `load_extension`. They are checked before and after comment stripping
|
|
@@ -357,6 +358,16 @@ A placeholder whose name ends in `.example`, `.sample`, `.template` or
|
|
|
357
358
|
characters, still does, and so does a path pattern (one with a `/`, such as
|
|
358
359
|
`.ssh/*`), which covers everything under it.
|
|
359
360
|
|
|
361
|
+
A pattern reads the way a `.gitignore` line does. One with a `/` names its
|
|
362
|
+
file below the root as well, so `config/database.yml` covers an engine's, a
|
|
363
|
+
pack's and a test/dummy app's copy, and a leading `/` anchors it at the root. A
|
|
364
|
+
trailing `/` names a folder and everything in it, and `**/` stands for any
|
|
365
|
+
number of folders, none included, so `**/id_rsa` covers an `id_rsa` at the
|
|
366
|
+
root too. A file a link brings in from the app's repository is also asked by
|
|
367
|
+
its name there, so a link to a pack's `config/` folder cannot hide the
|
|
368
|
+
`config/` a pattern looks for. A path a caller names that is longer than 4096
|
|
369
|
+
bytes, which no file system holds, is answered as not found.
|
|
370
|
+
|
|
360
371
|
### AI context file exclusions
|
|
361
372
|
|
|
362
373
|
Search also excludes generated AI context files to prevent circular references:
|
|
@@ -376,6 +387,28 @@ AGENTS.md, opencode.json
|
|
|
376
387
|
config.sensitive_patterns = %w[.env* *.key *.pem credentials.yml.enc]
|
|
377
388
|
```
|
|
378
389
|
|
|
390
|
+
On the static tier (`--no-boot`, or a boot that failed) no initializer runs.
|
|
391
|
+
The gem reads every Ruby file of the app that names it (`RailsAiContext` or
|
|
392
|
+
`rails_ai_context`), however the boot loads it, without running it: a file
|
|
393
|
+
under a `config/` folder, and the top level and class bodies of any other file,
|
|
394
|
+
which run as the file loads, whole; the body of a method outside `config/`, a
|
|
395
|
+
tool's or a helper's that runs when called, for what it writes. Tests (a `spec/` or `test/` folder at any
|
|
396
|
+
depth, an engine's test app among them), `db/`, `tmp/`, `vendor/bundle/`,
|
|
397
|
+
`vendor/ruby/`, `vendor/cache/`, `vendor/gems/`, an installed copy of the gem, a
|
|
398
|
+
generator's templates, hidden files and links are left out, and
|
|
399
|
+
`config/initializers` and `config/environments` are read through a link as
|
|
400
|
+
Rails globs them. The literal values those files write to
|
|
401
|
+
`sensitive_patterns`, `excluded_paths` and `skip_tools` are added to the YAML's;
|
|
402
|
+
a removal is left out. A value it cannot read, such as one built from a
|
|
403
|
+
constant or the environment, the configuration handed to other code or
|
|
404
|
+
reached through `send`, a pattern changed in place, or a file that does not
|
|
405
|
+
parse, stops the static tier with an error that names the file and line;
|
|
406
|
+
outside a method, so does a list or one of its patterns kept or handed on. A
|
|
407
|
+
file outside the app, a list a method keeps and changes later, and code that reaches
|
|
408
|
+
the settings through a name it builds at run time
|
|
409
|
+
(`const_get("Rails" + "AiContext")`) are not followed.
|
|
410
|
+
`.rails-ai-context.yml` is read on every tier.
|
|
411
|
+
|
|
379
412
|
---
|
|
380
413
|
|
|
381
414
|
## Path traversal protection
|
|
@@ -516,7 +549,7 @@ Complex patterns that would cause catastrophic backtracking raise `RegexpError`
|
|
|
516
549
|
|
|
517
550
|
`SafeFile.read` provides drop-in safety for all file reads:
|
|
518
551
|
|
|
519
|
-
- Size limit enforcement (`config.max_file_size`, default: 5 MB)
|
|
552
|
+
- Size limit enforcement (`config.max_file_size`, default: 5 MB). `rails_search_code` skips a file over it on both backends, so a row is never shown without its whole file's redaction, and a file named as its path answers `File too large`
|
|
520
553
|
- Returns `nil` on any failure (no exceptions leak)
|
|
521
554
|
- UTF-8 encoding with invalid/undefined byte replacement
|
|
522
555
|
- Handles: ENOENT, EACCES, EISDIR, ENAMETOOLONG, SystemCallError
|
|
@@ -532,6 +565,8 @@ What gets redacted:
|
|
|
532
565
|
- Passwords, tokens, secrets and API keys, wherever they are named
|
|
533
566
|
- The same in the app's own source a tool or resource shows: a method body, a template, a test file, a scan's code line, a change's diff (where a file on `sensitive_patterns` shows none of its lines)
|
|
534
567
|
- Credentials embedded in URIs (`redis://user:pass@host`)
|
|
568
|
+
- Values a logged SQL statement holds for a secret column: compared with it (through `LOWER()`, `CAST()` or a JSON path too), listed for it with `IN` or `BETWEEN`, inserted under it, or bound to a `$1` or `?` placeholder for it. MySQL and MariaDB log every value inline, and `insert_all` and `upsert_all` do on every adapter. A value whose column cannot be told is filtered when the statement names a secret column, and every value of an `INSERT` with no column list is filtered. A secret-named key in JSON a value holds is filtered, its quotes escaped or not. A line with `\'` in it that holds no backquoted name outside its strings is read both as MySQL and as standard SQL. Each string of a JSON log line, a header before it or not, is read as a line of its own; a value under a secret's key is filtered, an array's included, and so are a JSON logger's binds when the statement beside them names a secret column. A line is read on its own, so a value with a line break in it is read a line at a time
|
|
569
|
+
- The values a database error repeats: PostgreSQL's failing row whole, since it names no column, and its duplicate key when the key names a secret; MySQL's and MariaDB's duplicate entry, unless Rails named the index after columns that name no secret (`index_users_on_email`); and the excerpt of the statement a MySQL syntax error quotes, whole. A message cut off is filtered to the end of its line. An error in a language other than English is read as any other line
|
|
535
570
|
- Email addresses in log lines, in any script (`jürgen@example.com`, `anna@münchen.de`)
|
|
536
571
|
- Values assigned to secret-named settings in initializers
|
|
537
572
|
|
|
@@ -596,7 +631,7 @@ The engine builds one transport per process, under a mutex, outside the controll
|
|
|
596
631
|
## Credential handling
|
|
597
632
|
|
|
598
633
|
- `rails_get_config(section:"env")` returns credential **keys** and no values
|
|
599
|
-
- The process's environment variable values are never read. A default written in code, and a placeholder from `.env.example`, `.env.sample` or `.env.template`, is shown after redaction
|
|
634
|
+
- The process's environment variable values are never read. A default written in code, and a placeholder from `.env.example`, `.env.sample` or `.env.template`, is shown after redaction. A default in code, an example file's value and a Dockerfile `ENV` or `ARG` value read `[FILTERED]` when their shape can carry a secret: a URL whose user part holds a password or is a key (16 or more letters and digits, or hex in groups, as a DSN's is), a webhook's address, a token in a query, a run of 16 or more letters and digits with both in it, and a hex key split into groups. `/`, `.`, `-` and `_` end a run, so a path, a host and a slug show; so does a key written with them, base64 with a `/` among them, or in letters alone, unless its name says it is one. A class name with a digit in a run of 16 reads `[FILTERED]`. A `BUNDLE_<HOST>` variable, which Bundler reads a gem server's password from, is hidden whatever it holds. An example file's inline comment is hidden when it holds a credential, one of these shapes or a hex key in groups among its words, and a `#` starts a comment only after a space, so a password's `#` stays in its value (dotenv itself cuts an unquoted value at any `#`). A Kamal clear value that holds any URL, or a run of 16 letters and digits with `/` in it, is hidden
|
|
600
635
|
- `config/credentials/*.yml.enc` is in the sensitive patterns list
|
|
601
636
|
|
|
602
637
|
---
|
data/docs/SETUP.md
CHANGED
|
@@ -91,6 +91,197 @@ This is what the generator writes for an in-Gemfile install. A standalone instal
|
|
|
91
91
|
|
|
92
92
|
On Windows the command is `cmd` and `"/c"` goes ahead of the args (`["/c", "bundle", "exec", "rails-ai-context", "serve"]`): a gem's executables there are batch files, which a client that starts its server without a shell does not run. The generator writes that form on Windows, and `doctor` names an entry written for the other platform; see [GUIDE.md](GUIDE.md#mcp-server-setup) for teams on both.
|
|
93
93
|
|
|
94
|
+
### As a plugin
|
|
95
|
+
|
|
96
|
+
The repository is also a Claude Code plugin marketplace. The plugin brings the
|
|
97
|
+
MCP server and, at the start of each session in a Rails app, the six rules the
|
|
98
|
+
context files open with. It writes no context files or MCP config, so it suits
|
|
99
|
+
a team that keeps its own `CLAUDE.md`; the install records it in
|
|
100
|
+
`.claude/settings.json`.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
gem install rails-ai-context # the plugin runs the installed gem
|
|
104
|
+
claude plugin install rails-ai-context --marketplace crisnahine/rails-ai-context --scope project
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
In a session the second line is `/plugin install rails-ai-context --marketplace crisnahine/rails-ai-context`.
|
|
108
|
+
A Claude Code too old for `--marketplace` takes two steps:
|
|
109
|
+
`/plugin marketplace add crisnahine/rails-ai-context`, then
|
|
110
|
+
`/plugin install rails-ai-context@rails-ai-context`. Project scope records the
|
|
111
|
+
plugin in `.claude/settings.json` of the folder you run it in, so run it in the
|
|
112
|
+
app's folder; commit that file, and add the marketplace there too so a
|
|
113
|
+
teammate's Claude Code offers to install it:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"enabledPlugins": { "rails-ai-context@rails-ai-context": true },
|
|
118
|
+
"extraKnownMarketplaces": {
|
|
119
|
+
"rails-ai-context": { "source": { "source": "github", "repo": "crisnahine/rails-ai-context" } }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
A teammate who has the committed setting runs `claude plugin install
|
|
125
|
+
rails-ai-context --marketplace crisnahine/rails-ai-context --scope local`,
|
|
126
|
+
which leaves the committed file as it is.
|
|
127
|
+
|
|
128
|
+
- **Where it runs.** Claude Code reads `.claude/settings.json` only from the
|
|
129
|
+
folder a session starts in, so project scope reaches sessions started in the
|
|
130
|
+
app's folder. For sessions started in a folder inside the app, or in a
|
|
131
|
+
monorepo's app folder from the repository's root, enable it with `--scope
|
|
132
|
+
local` (the git repository's `settings.local.json`) or at user scope. The
|
|
133
|
+
server then reads the app Claude Code was started in, the app above that
|
|
134
|
+
folder, or the one app below it, as every command does. At user scope the
|
|
135
|
+
plugin starts in every project; outside a Rails app its server connects with
|
|
136
|
+
no tools and tells the session why. In a folder that holds several apps it
|
|
137
|
+
says to start Claude Code in one of them; for a session over all of them,
|
|
138
|
+
use the [workspace](CLI.md#init-standalone-only) entries `init` writes, and
|
|
139
|
+
turn the plugin off for that folder with `claude plugin disable
|
|
140
|
+
rails-ai-context@rails-ai-context --scope project` run there, or `--scope
|
|
141
|
+
local` where a `--scope local` install turns it on. On macOS and Linux a
|
|
142
|
+
local setting written at a git repository's root reaches every session in
|
|
143
|
+
that repository, the apps' too. On Windows, and where the repository's root
|
|
144
|
+
is your home folder or belongs to another user, a local setting reaches only
|
|
145
|
+
sessions started in the folder it was written in. Where `init` at a
|
|
146
|
+
repository's root wrote an app's own `.mcp.json`, which sessions started in
|
|
147
|
+
the app read, a `--scope local` install that reaches those sessions runs the
|
|
148
|
+
plugin beside it; `doctor` names the one to turn off.
|
|
149
|
+
- **The gem comes first.** The plugin needs rails-ai-context 5.35.1 or later,
|
|
150
|
+
installed for the `ruby` on Claude Code's `PATH` (Ruby 3.1 or later); `gem
|
|
151
|
+
update rails-ai-context` brings an older one up. A shim version manager
|
|
152
|
+
(rbenv, asdf, or mise with its shims on `PATH`) picks that Ruby by the folder
|
|
153
|
+
Claude Code starts in: that folder's `.ruby-version` or `.tool-versions`, or
|
|
154
|
+
the global one, so a session started above the app runs that folder's Ruby. A
|
|
155
|
+
shell-hook one (rvm, chruby, `mise activate`) leaves the Ruby of the shell
|
|
156
|
+
that started Claude Code, and a Claude Code started from the Dock or an IDE
|
|
157
|
+
may see the system Ruby instead. An app that also has the gem in its Gemfile
|
|
158
|
+
gets its bundled copy, as with any standalone install, a bundle kept under
|
|
159
|
+
its own path such as `vendor/bundle` too. A copy from git is the exception:
|
|
160
|
+
the installed copy serves and the app's boot loads the git copy over it, so
|
|
161
|
+
point the Gemfile at a release or a `path:` checkout. When the plugin cannot
|
|
162
|
+
serve, its server still connects, with no tools, and tells the session why,
|
|
163
|
+
naming the Ruby and the folder: no gem for that Ruby, an older one, a Ruby
|
|
164
|
+
too old, a bundle Claude Code inherited from `bundle exec` that lacks the
|
|
165
|
+
gem, several apps or none. The same line is the server's stderr, which
|
|
166
|
+
`claude --debug` shows. A start that fails after that, from the gem's own
|
|
167
|
+
error or an inherited bundle not installed for that Ruby (Bundler's trace,
|
|
168
|
+
[TROUBLESHOOTING.md](TROUBLESHOOTING.md#mcp-server-fails-to-start)), is
|
|
169
|
+
listed as failed in `/mcp`, and Claude Code then skips the server in new
|
|
170
|
+
sessions for about 15 minutes: reconnect it in `/mcp` once the cause is
|
|
171
|
+
fixed. A session gets no rules while the server has no tools for them to
|
|
172
|
+
name.
|
|
173
|
+
- **Install the gem, not its setup, for Claude Code.** Where the app's
|
|
174
|
+
`.claude/settings.json` turns the plugin on, the install generator and
|
|
175
|
+
`init` write no `.mcp.json` server and say why, and so does `init` in a
|
|
176
|
+
folder holding one app, where that folder's own `.claude/settings.json`
|
|
177
|
+
turns it on; it still writes the app's own `.mcp.json` for sessions started
|
|
178
|
+
in the app, which do not read that folder's settings, and says so. Where
|
|
179
|
+
only your own settings turn it on, they still write one for the team, and
|
|
180
|
+
their next step says to turn it off for yourself with `/mcp`.
|
|
181
|
+
- **The plugin or another server, not both.** Claude Code starts the plugin's
|
|
182
|
+
server and one from `.mcp.json` or `claude mcp add`, an HTTP entry under the
|
|
183
|
+
gem's name included, as two servers and lists every tool twice. `doctor`
|
|
184
|
+
reads Claude Code the way a session does, by the folder it started in: that
|
|
185
|
+
folder's settings and the local settings that reach it (its git
|
|
186
|
+
repository's, a linked worktree's being its repository's, as "Where it runs"
|
|
187
|
+
says), every `.mcp.json` from that folder up, the `/mcp` toggles and local
|
|
188
|
+
servers Claude Code keeps for the repository, the MCP policy in
|
|
189
|
+
`allowedMcpServers` and `deniedMcpServers` (by name, command or url), and a
|
|
190
|
+
deployed `managed-mcp.json`, whose servers are then the only ones Claude
|
|
191
|
+
Code starts, each still subject to `/mcp` and `deniedMcpServers`, and to
|
|
192
|
+
`allowedMcpServers` where its entry expands a `${VAR}`, and the marketplace
|
|
193
|
+
policy in `strictKnownMarketplaces` and `blockedMarketplaces`. It follows a
|
|
194
|
+
`CLAUDE_CONFIG_DIR` set in your settings' `env` block, as Claude Code does.
|
|
195
|
+
It does not see settings an organization delivers from the Claude console,
|
|
196
|
+
through MDM or the Windows registry, and it reads the managed settings files
|
|
197
|
+
as if all of them were in force; where policy comes that way, its MCP
|
|
198
|
+
verdicts can be wrong. It checks the shape of `enabledPlugins` only, while
|
|
199
|
+
Claude Code ignores a whole settings file that breaks its schema elsewhere
|
|
200
|
+
and says so when it starts: fix such a file first. It looks at the app, the
|
|
201
|
+
folder `doctor` runs in, the folders above the app that hold it alone, and
|
|
202
|
+
the folders Claude Code keeps projects for. The plugin counts where it is
|
|
203
|
+
enabled, installed for that project, not turned off in `/mcp` and not
|
|
204
|
+
blocked by policy; then Claude Code's MCP check passes without a
|
|
205
|
+
`.mcp.json`, and with no AI tools chosen it checks no other tool's config
|
|
206
|
+
unless that config is broken. Where the plugin runs only for sessions
|
|
207
|
+
started in another folder, `doctor` names that folder and what turns it on
|
|
208
|
+
for this one. Where Claude Code never ran, as on CI, what the plugin needs
|
|
209
|
+
is a warning, not a failure. Where it runs nowhere, `doctor` names why: not
|
|
210
|
+
installed (with the install that leaves a committed file alone), a local
|
|
211
|
+
marketplace folder that is gone, its server turned off in `/mcp`, the MCP or
|
|
212
|
+
marketplace policy, a managed settings file Claude Code cannot read, or a
|
|
213
|
+
`CLAUDE_CONFIG_DIR` that is empty or not an absolute path, set in the
|
|
214
|
+
environment or a settings `env` block (`doctor` names which), which leaves
|
|
215
|
+
each session reading its own folder's. A second server counts where such a
|
|
216
|
+
session would start it, and `doctor` names the command that removes it and
|
|
217
|
+
the folder to run it in: a `.mcp.json` entry comes out where the committed
|
|
218
|
+
settings turn the plugin on, and is turned off with `/mcp` where only your
|
|
219
|
+
own settings do.
|
|
220
|
+
- **The rules load once.** The hook skips them where Claude Code already loads
|
|
221
|
+
the generator's rules for that session: from the app's `CLAUDE.md` or
|
|
222
|
+
`.claude/rules/rails-mcp-tools.md`, or from its `AGENTS.md` where
|
|
223
|
+
`CLAUDE.md` imports it or no `CLAUDE.md` is on the way up from the folder
|
|
224
|
+
the session started in, or from a `CLAUDE.md`, `.claude/CLAUDE.md` or
|
|
225
|
+
`CLAUDE.local.md` on that way up that carries them, as an engine's above its
|
|
226
|
+
test/dummy app does (your own `~/.claude/CLAUDE.md` does not count). It
|
|
227
|
+
skips them too where `.rails-ai-context.yml` or a configure block in
|
|
228
|
+
`config/application.rb`, the environment's file or an initializer sets
|
|
229
|
+
`anti_hallucination_rules` to `false` or `nil`; a block's value wins over
|
|
230
|
+
the file's, as when the app boots. It reads that Ruby without running it, so
|
|
231
|
+
only a literal that ends its statement counts, and rule 3 leaves out the
|
|
232
|
+
`rails_get_config` sections `skip_tools` turns off, in the YAML or a literal
|
|
233
|
+
list, assigned or appended (`=`, `<<` chained or not, `+=`, `|=`, `push`,
|
|
234
|
+
`append`, `concat`); a symbol appended in place, a Set or a value built at
|
|
235
|
+
boot is not read, as the server skips none of those either. A session
|
|
236
|
+
started above the app gets the rules from the hook, and once a file in the
|
|
237
|
+
app is read, Claude Code also loads that app's `CLAUDE.md`, so such a
|
|
238
|
+
session can hold them twice. So can a session resumed after the app's
|
|
239
|
+
`CLAUDE.md` gained the rules (from `init`, `context` or a pull), until
|
|
240
|
+
`/clear` or a compaction.
|
|
241
|
+
- **Windows works the same.** The server starts `ruby` without a shell, so it
|
|
242
|
+
needs no `cmd /c`.
|
|
243
|
+
- **One allowlist entry lasts.** The server's command line names no plugin
|
|
244
|
+
folder, so no update changes it, and under an `allowedMcpServers` list this
|
|
245
|
+
entry admits it (`doctor` names it where it is missing). It admits any plugin
|
|
246
|
+
that ships the same line, too. Where the list admits other stdio servers by
|
|
247
|
+
`serverName`, adding this entry switches every stdio server to matching by
|
|
248
|
+
command, so each of them needs a `serverCommand` entry as well; `doctor`
|
|
249
|
+
says so. Naming no script also keeps rbenv from picking the Ruby by the
|
|
250
|
+
plugin's own folder.
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
{"serverCommand":["ruby","-e","load File.join(ENV.fetch('CLAUDE_PLUGIN_ROOT'), 'scripts', 'serve.rb')"]}
|
|
254
|
+
```
|
|
255
|
+
- **Permission rules name the plugin's server.** Its tools are
|
|
256
|
+
`mcp__plugin_rails-ai-context_rails-ai-context__<tool>`, so a rule for the
|
|
257
|
+
`.mcp.json` server (`mcp__rails-ai-context`) does not cover them;
|
|
258
|
+
`mcp__plugin_rails-ai-context_rails-ai-context` allows them all.
|
|
259
|
+
- **A slow app's boot.** The plugin's server entry is the plugin's own, so set
|
|
260
|
+
`RAILS_AI_CONTEXT_BOOT_TIMEOUT` and `MCP_TIMEOUT` in Claude Code's settings
|
|
261
|
+
`env` (user, project or local) or in the shell that starts Claude Code; see
|
|
262
|
+
[TROUBLESHOOTING.md](TROUBLESHOOTING.md#answers-say-boot-did-not-finish-within-20s).
|
|
263
|
+
- **Updates follow this repository.** The plugin's version is the commit it was
|
|
264
|
+
installed from. Claude Code updates it only with auto-update on (under
|
|
265
|
+
`/plugin`, Marketplaces) or `claude plugin update
|
|
266
|
+
rails-ai-context@rails-ai-context`. The server is versioned by the gem: `gem
|
|
267
|
+
update rails-ai-context`, then reconnect the server in `/mcp` or start a new
|
|
268
|
+
session. Run `gem cleanup` only after that: a running server loads its files
|
|
269
|
+
as it needs them, so once its copy is gone, a call that needs a file it has
|
|
270
|
+
not loaded yet answers with a request to reconnect it.
|
|
271
|
+
- **Removing it** from one app is `claude plugin disable
|
|
272
|
+
rails-ai-context@rails-ai-context --scope local` run there, which wins over
|
|
273
|
+
your own and the committed settings for every session the local setting
|
|
274
|
+
reaches ("Where it runs"). Taking an install out is `claude plugin uninstall
|
|
275
|
+
rails-ai-context@rails-ai-context --scope <scope>`, with the scope it went in
|
|
276
|
+
at, run in the folder it was installed from: once for a user install, once
|
|
277
|
+
per project for a project or local one. A marketplace added from a local
|
|
278
|
+
folder or file runs the plugin with no install, so there only the disable
|
|
279
|
+
turns it off. The gem's `uninstall` leaves Claude Code's
|
|
280
|
+
settings alone and names those commands, one disable per repository whose
|
|
281
|
+
sessions run the plugin, before it suggests removing the gem, which the
|
|
282
|
+
plugin runs; where managed settings turn the plugin on, or a deployed
|
|
283
|
+
`managed-mcp.json` runs the gem, it says to ask whoever manages them.
|
|
284
|
+
|
|
94
285
|
### Split rules with `paths:` frontmatter
|
|
95
286
|
|
|
96
287
|
Claude Code loads `.claude/rules/` files conditionally based on YAML frontmatter:
|