rails-ai-context 5.32.1 → 5.32.2
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 +74 -0
- data/CONTEXT.md +10 -8
- data/CONTRIBUTING.md +2 -2
- data/README.md +14 -11
- data/SECURITY.md +4 -4
- data/docs/ARCHITECTURE.md +11 -10
- data/docs/CLI.md +33 -7
- data/docs/COMPATIBILITY.md +48 -32
- data/docs/CONFIGURATION.md +10 -8
- data/docs/CUSTOM_TOOLS.md +8 -8
- data/docs/FAQ.md +6 -5
- data/docs/GUIDE.md +154 -80
- data/docs/INTROSPECTORS.md +24 -16
- data/docs/QUICKSTART.md +14 -3
- data/docs/RECIPES.md +50 -40
- data/docs/SECURITY.md +23 -13
- data/docs/SETUP.md +23 -26
- data/docs/STANDALONE.md +7 -3
- data/docs/TOOLS.md +79 -45
- data/docs/TROUBLESHOOTING.md +12 -11
- data/exe/rails-ai-context +8 -4
- data/lib/generators/rails_ai_context/install/install_generator.rb +1 -1
- data/lib/rails_ai_context/configuration.rb +2 -2
- data/lib/rails_ai_context/install/ai_tool.rb +11 -2
- data/lib/rails_ai_context/install/cleanup.rb +69 -23
- data/lib/rails_ai_context/install/program.rb +4 -1
- data/lib/rails_ai_context/install/selection_record.rb +20 -2
- data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +2 -2
- data/lib/rails_ai_context/server.rb +2 -2
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +12 -4
- data/lib/rails_ai_context/test_helper.rb +2 -0
- data/lib/rails_ai_context/tools/base_tool.rb +3 -1
- data/lib/rails_ai_context/tools/dependency_graph.rb +2 -2
- data/lib/rails_ai_context/tools/get_concern.rb +1 -1
- data/lib/rails_ai_context/tools/get_env.rb +1 -1
- data/lib/rails_ai_context/tools/get_gems.rb +2 -2
- data/lib/rails_ai_context/tools/query.rb +107 -39
- data/lib/rails_ai_context/tools/search_code.rb +20 -16
- data/lib/rails_ai_context/version.rb +1 -1
- data/server.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fac0badbc6cdd650df304ed443948b0d96190d77458caebca44b513642d28bd9
|
|
4
|
+
data.tar.gz: 52c14b40304a1fb7e475bfc8755e22954afd655d3e31607d08814c187bd5b05c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 139928d253882921153d4f676aa7b7317efb5a4396dcbd4fc9d90ba8cc9abb5f513481b1534a22b18319c731f6b6a4e99fc1b21145daf03f0b980c2fc337c2cf
|
|
7
|
+
data.tar.gz: 122546a2d53a798334bb16f27886300eda3b76a958e3ded63f5f2814257bcbe69b568c585eefb0b81e4cf81d63525412fcef8acddd24ed45a4df1a588bebcbfe
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,80 @@ 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.32.2] - 2026-10-07
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
- **`rails_query` runs the text its checks read.** The read-only validator
|
|
13
|
+
strips comments before it looks for blocked keywords, sensitive columns and
|
|
14
|
+
a second statement, but the query that ran was the raw input. The stripper
|
|
15
|
+
did not know string literals, so a `--` or `/*` inside a quoted string hid
|
|
16
|
+
the rest of the line from every check while the database still ran it: a
|
|
17
|
+
column such as `api_key` could be read through an alias, unredacted. Comments
|
|
18
|
+
are now taken out by a scanner that copies quoted strings and identifiers
|
|
19
|
+
whole, and the query that runs is the text it leaves, so the checks and the
|
|
20
|
+
database read the same thing.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **The row limit holds whatever the query text looks like.** `LIMIT` was
|
|
25
|
+
appended after a trailing `-- note`, where the database read it as part of
|
|
26
|
+
the comment, and the first `LIMIT` anywhere in the text counted as the
|
|
27
|
+
query's own: one in a comment or a string literal left the query unlimited,
|
|
28
|
+
and one in a subquery was rewritten to the cap, which changed the answer.
|
|
29
|
+
Only a `LIMIT` or `FETCH FIRST` that ends the statement is the query's own
|
|
30
|
+
now, the appended one goes on its own line, and the answer is cut to the
|
|
31
|
+
limit after the database returns as well.
|
|
32
|
+
- **A `#` no longer ends a PostgreSQL or SQLite query line.** A line starting
|
|
33
|
+
with `#>>`, `#>` or `#-` was dropped from the validated text as a MySQL
|
|
34
|
+
comment. Hash comments are read on MySQL only, where they can start anywhere
|
|
35
|
+
on a line.
|
|
36
|
+
- **Dropping an AI tool on a re-run removes only what the gem generated.**
|
|
37
|
+
Answering yes to "Remove their generated files?" deleted the whole rules
|
|
38
|
+
directory and the whole root file, so a rule file written by hand in
|
|
39
|
+
`.claude/rules/`, and a `CLAUDE.md` the gem had never written to, went with
|
|
40
|
+
them. Cleanup now removes the rule files the gem names, the block between
|
|
41
|
+
its markers in a root file, and a split rules file it generated whole. It
|
|
42
|
+
leaves every other file and line, a symlink included, and says so when a
|
|
43
|
+
file kept the user's own content.
|
|
44
|
+
- **`search_code` trace names the routes that reach the call.** A controller
|
|
45
|
+
caller's heading listed the controller's first two routes whatever action
|
|
46
|
+
the call was in, so a call inside `create` could read `GET /posts`. The
|
|
47
|
+
heading now lists the routes of the actions the calls sit in, and none when
|
|
48
|
+
the call is in a `before_action` line, a private or class method, or a
|
|
49
|
+
nested class.
|
|
50
|
+
- **`rails-ai-context init` records the tool mode where it is read first.**
|
|
51
|
+
It wrote `tool_mode` to `.rails-ai-context.yml` only, and an initializer that
|
|
52
|
+
already set `config.tool_mode` won on read, so choosing CLI mode changed
|
|
53
|
+
nothing in such an app. It now updates the initializer line too, as it does
|
|
54
|
+
for the tool selection. An assignment written in another shape, such as one
|
|
55
|
+
read from `ENV`, is left alone and no second line is added beside it; the
|
|
56
|
+
same now holds for `config.context_files`.
|
|
57
|
+
- **`--defaults` on the install generator asks nothing.** The legacy-files
|
|
58
|
+
question still waited for an answer on a terminal.
|
|
59
|
+
- **`rails-ai-context init --mcp-only` no longer asks about legacy rule
|
|
60
|
+
files**, matching the generator.
|
|
61
|
+
- **`rails_get_gems` filters by every category it prints.** The `category`
|
|
62
|
+
parameter accepted 8 of the 19 categories, so `monitoring`, `admin`,
|
|
63
|
+
`payments` and the rest could not be asked for.
|
|
64
|
+
- **A composed answer carries the static tier banner once.** `rails_get_context`
|
|
65
|
+
repeated it for each tool the answer was built from.
|
|
66
|
+
- **`rails_ai_context/test_helper` loads the MCP SDK.** A spec for a custom
|
|
67
|
+
tool raised `uninitialized constant MCP` when the helper was the first thing
|
|
68
|
+
the suite required.
|
|
69
|
+
- **Descriptions say what the code does.** `rails_dependency_graph` no longer
|
|
70
|
+
claims services and controllers, `rails_get_concern` no longer promises a
|
|
71
|
+
`summary` level that differs from `standard`, `rails ai:context:codex` no
|
|
72
|
+
longer says it writes `.codex/config.toml`, and `init --help` no longer names
|
|
73
|
+
`.mcp.json` as the one MCP config.
|
|
74
|
+
|
|
75
|
+
- **The docs say what the code does.** They were checked against the code,
|
|
76
|
+
line by line. The install steps
|
|
77
|
+
are listed in full in the guide, MCP config samples show the command the
|
|
78
|
+
installer writes, every tool's parameters match its schema, and counts,
|
|
79
|
+
defaults and error messages are the ones the code has. `rails ai:setup`,
|
|
80
|
+
which was documented and never existed, is gone from the CLI reference.
|
|
81
|
+
|
|
8
82
|
## [5.32.1] - 2026-10-06
|
|
9
83
|
|
|
10
84
|
### Fixed
|
data/CONTEXT.md
CHANGED
|
@@ -6,7 +6,7 @@ Terms this project uses in a narrower sense than everyday English. One entry per
|
|
|
6
6
|
|
|
7
7
|
Overloaded. Always qualify it; never use "environment" bare in a tool name, an introspector name, or a config key.
|
|
8
8
|
|
|
9
|
-
**Process environment** - the environment variables a running app sees, plus the places they are declared (`ENV[]` call sites, `.env.example`, Dockerfile, credentials keys). Served by `rails_get_env
|
|
9
|
+
**Process environment** - the environment variables a running app sees, plus the places they are declared (`ENV[]` call sites, `.env.example`, Dockerfile, credentials keys). Served by `rails_get_env`: `EnvIntrospector` reads the variables and the `ENV[]` call sites, and the tool reads the other places itself.
|
|
10
10
|
|
|
11
11
|
**Environment config** - what `config/environments/*.rb` declares per environment: the assigned `config.*` keys and the values of the notable toggles. Served by `rails_get_env_config`, read by `EnvConfigIntrospector`.
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ The assistant a user points at their app: claude, cursor, copilot, opencode, cod
|
|
|
18
18
|
|
|
19
19
|
Avoid "format" for either sense - claude is not a file format, and the word collides with real formatting elsewhere in the code. The public config key `ai_tools` already uses the canonical term.
|
|
20
20
|
|
|
21
|
-
`Install::AiTool` is the one table of what each means: name, context files, MCP config shape, legacy leftovers. `Install::SelectionRecord` owns which ones the user picked - written to YAML always and to the initializer line
|
|
21
|
+
`Install::AiTool` is the one table of what each means: name, context files, MCP config shape, legacy leftovers. `Install::SelectionRecord` owns which ones the user picked - written to YAML always, and to the initializer line when the caller asks for it and the line is in a shape it can rewrite (an ordinary `ai:context` run refreshes the YAML only), read initializer-first because that is the file a user hand-edits.
|
|
22
22
|
|
|
23
23
|
## Path
|
|
24
24
|
|
|
@@ -30,13 +30,13 @@ Two senses, one per module, and neither is bare "path" in a name.
|
|
|
30
30
|
|
|
31
31
|
## Safe path
|
|
32
32
|
|
|
33
|
-
A caller-supplied path resolved once, through `SafePath`, before anything reads it. Its refusal order is the contract, not an implementation detail: traversal, sensitive name, realpath, containment, sensitive realpath, file, size. A caller that reorders those checks, or repeats one of them itself, gets a different answer on a symlink or a dotfile than every other tool does, which is the divergence the module exists to end. `safe_glob` is the globbed-path form: the
|
|
33
|
+
A caller-supplied path resolved once, through `SafePath`, before anything reads it. Its refusal order is the contract, not an implementation detail: traversal, sensitive name, realpath, containment, sensitive realpath, file, size. A caller that reorders those checks, or repeats one of them itself, gets a different answer on a symlink or a dotfile than every other tool does, which is the divergence the module exists to end. `BaseTool.safe_glob` is the globbed-path form: realpath, containment and the sensitive-realpath check on each path a pattern yields, with the size cap left to the read that follows.
|
|
34
34
|
|
|
35
35
|
Not the same as a path that merely looks harmless, and not a caller's own containment check - "the tool guards this parameter" means it hands the parameter to `SafePath` and renders whatever refusal comes back.
|
|
36
36
|
|
|
37
37
|
## Carried path
|
|
38
38
|
|
|
39
|
-
A path the payload already holds because the gem's own walk found it - a controller's or a model's `file:`.
|
|
39
|
+
A path the payload already holds because the gem's own walk found it - a controller's or a model's `file:`. A controller's is re-read with `SafeFile.read(File.join(root, relative))`, which checks that it is a regular file under the size cap and nothing else; a model's goes through `PortablePath.resolve` first, since it can be a `gem:` path. The reason is the walk: `SourceScan` deliberately keeps the spelling the app uses when the realpath falls outside the root, so a pack or an in-repo engine symlinked out of the root is spelled inside it, and `SafePath`'s realpath containment would refuse the very file the payload just named - the source comes back nil and a section silently empties.
|
|
40
40
|
|
|
41
41
|
Not an exception to the **safe path** rule, the other side of it: a caller-supplied path is untrusted and goes through `SafePath`; a carried path was produced by this gem and only needs the cap.
|
|
42
42
|
|
|
@@ -58,7 +58,7 @@ The other half of the same problem is the reverse trip. Once a name is the decla
|
|
|
58
58
|
|
|
59
59
|
The table a model reads. Rails builds it as `table_name_prefix` + the demodulized class name, pluralized unless `pluralize_table_names` is off, + `table_name_suffix`, unless the class assigns one itself or is an STI child, which reads its parent's. The static tier answers the same order from source: an explicit `self.table_name =`, else the table of the model it inherits from, else the prefix and suffix the enclosing modules declare around the file's own basename. That basename is pluralized unless the class, then its superclass chain, then the app sets `pluralize_table_names = false`. The app-wide affixes and pluralize setting come from `ActiveRecordSettings`, which reads config/application.rb, the environment file, the initializers and `on_load(:active_record)` blocks. Never the underscored constant - that turns `Admin::ActionLog` into `admin/action_logs` and `OAuthClientConfig` into `o_auth_client_configs`, neither of which is a table.
|
|
60
60
|
|
|
61
|
-
`TableName` reads the five declarations of one class body (`table_name`, `table_name_prefix`, `table_name_suffix`, `pluralize_table_names` and `primary_key`) and holds the two derivations; walking the namespace and the superclass chain belongs to the caller, because only it has the other files. A reader that starts from a model name instead of a file asks `TableName.for_model_name`, which takes the table the model tier already recorded before falling back to the convention. A prefix declared outside the model directories is still invisible, so the answer stays [STATIC].
|
|
61
|
+
`TableName` reads the five declarations of one class body (`table_name`, `table_name_prefix`, `table_name_suffix`, `pluralize_table_names` and `primary_key`) and holds the two derivations; walking the namespace and the superclass chain belongs to the caller, because only it has the other files. A reader that starts from a model name instead of a file asks `TableName.for_model_name`, which takes the table the model tier already recorded before falling back to the convention. A module prefix declared outside the model directories is still invisible, an engine's in `lib/**/engine.rb` aside, so the answer stays [STATIC].
|
|
62
62
|
|
|
63
63
|
## Static tier
|
|
64
64
|
|
|
@@ -94,8 +94,10 @@ A Rack app the routing table attaches at a path, engine or not. `mount App =>
|
|
|
94
94
|
"/path"` is `match("/path", to: App, via: :all, anchor: false)` with a name
|
|
95
95
|
derived, so both spellings build the same endpoint and both are this. The
|
|
96
96
|
payload key is `mounted_engines` for the sections that predate the widening;
|
|
97
|
-
|
|
98
|
-
carries, which is what the count beside it has always
|
|
97
|
+
in the booted routes section it holds every controller-less, non-dynamic
|
|
98
|
+
endpoint the route set carries, which is what the count beside it has always
|
|
99
|
+
counted. The engines section, and the static routes section, hold the mounts
|
|
100
|
+
parsed from the route files.
|
|
99
101
|
|
|
100
102
|
Distinct from a **Rails engine**, which is a `Rails::Engine` subclass whether
|
|
101
103
|
or not anything mounts it, and which `rails_get_engines` lists separately under
|
|
@@ -139,7 +141,7 @@ Which filters a controller runs, and which of them a given action runs. `ActionF
|
|
|
139
141
|
|
|
140
142
|
**A block filter is named by its line.** `before_action { ... }` and `after_action -> { ... }` are callbacks of their own, listed as `block (line N)` and `lambda (line N)` (a lambda says so, as `Proc#lambda?` does, so it reads apart from a block on its line), N being the line the block opens on in the controller's own file; a block in any other file (a concern's class method, an ancestor's body) is `block (line N of app/controllers/concerns/cache_concern.rb)`, as two blocks on one line number in two files are two callbacks. The booted tier lists only a block the app wrote; a framework's or a gem's (`allow_browser`) is left out, as the static tier cannot see it. `skip_forgery_protection` is the `skip_before_action :verify_authenticity_token` Rails defines it as.
|
|
141
143
|
|
|
142
|
-
**What it cannot see is stated rather than guessed.** The walk reads `ApplicationController`'s file by the one name Rails fixes, so its filters are in the chain; a parent the listing does not hold and the app has no file for - a gem-owned base, or one an inflection renames - still ends the walk. `docs/COMPATIBILITY.md` says both halves.
|
|
144
|
+
**What it cannot see is stated rather than guessed.** The walk reads `ApplicationController`'s file by the one name Rails fixes, so its filters are in the chain; a parent the listing does not hold and the app has no file for - a gem-owned base other than Devise's or Doorkeeper's (there the walk resumes at the parent the initializer names), or one an inflection renames - still ends the walk. `docs/COMPATIBILITY.md` says both halves.
|
|
143
145
|
|
|
144
146
|
## Payload
|
|
145
147
|
|
data/CONTRIBUTING.md
CHANGED
|
@@ -35,7 +35,7 @@ lib/rails_ai_context/
|
|
|
35
35
|
3. Register it in `lib/rails_ai_context/introspector.rb` (the `INTROSPECTOR_MAP`)
|
|
36
36
|
4. `extend StaticTier` and declare one: `static_tier :files_only` if `#call` runs unchanged with nothing booted, `:runtime_only` if it needs live reflection, or `:alternate_source` plus a `static_call` reading elsewhere. A mapped introspector that declares nothing fails the suite
|
|
37
37
|
5. Add the key to the appropriate preset(s) in `Configuration::PRESETS` (`:full` is the default, `:standard` for core-only)
|
|
38
|
-
6. Write specs in `spec/lib/rails_ai_context/your_introspector_spec.rb`
|
|
38
|
+
6. Write specs in `spec/lib/rails_ai_context/introspectors/your_introspector_spec.rb`
|
|
39
39
|
|
|
40
40
|
## Adding a New MCP Tool
|
|
41
41
|
|
|
@@ -86,7 +86,7 @@ bundle exec rubocop --parallel # Lint check
|
|
|
86
86
|
|
|
87
87
|
`spec/fixtures/static_app` is the app the static tier is exercised against, and
|
|
88
88
|
`spec/lib/rails_ai_context/serializers/real_shape_smoke_spec.rb` pins its model
|
|
89
|
-
count in
|
|
89
|
+
count in three assertions (a `Models 7` match and `Models (7)` twice).
|
|
90
90
|
|
|
91
91
|
Adding or removing a fixture model means changing that spec in the same commit.
|
|
92
92
|
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://rubygems.org/gems/rails-ai-context)
|
|
9
9
|
[](https://github.com/crisnahine/rails-ai-context/actions)
|
|
10
10
|
[](https://registry.modelcontextprotocol.io)
|
|
11
|
-
[](https://github.com/crisnahine/rails-ai-context)
|
|
12
12
|
[](https://github.com/crisnahine/rails-ai-context)
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
**rails-ai-context** is a Ruby gem that turns your Rails app into the source of truth for AI coding assistants. Instead of guessing your schema, associations, routes and conventions from training data, the assistant asks your app: 45 read-only tools served over [MCP](https://modelcontextprotocol.io) or run from the CLI, plus generated context files for Claude Code, Cursor, GitHub Copilot, OpenCode and Codex CLI.
|
|
31
31
|
|
|
32
32
|
> [!TIP]
|
|
33
|
-
> Nothing to add to your Gemfile if you don't want to. `gem install rails-ai-context`, then `rails-ai-context init` inside any Rails app. It also works on an app that won't boot: pass `--no-boot` and
|
|
33
|
+
> Nothing to add to your Gemfile if you don't want to. `gem install rails-ai-context`, then `rails-ai-context init` inside any Rails app. It also works on an app that won't boot: pass `--no-boot` and the tools answer from the source files. The two that need a live app, `query` and `runtime_info`, say so.
|
|
34
34
|
|
|
35
35
|
## Why
|
|
36
36
|
|
|
@@ -63,7 +63,7 @@ You catch it, fix it, re-prompt, and something next to it breaks. The tokens are
|
|
|
63
63
|
## Features
|
|
64
64
|
|
|
65
65
|
- **45 read-only tools** for schema, models, controllers, routes, views, Stimulus, Turbo, jobs, services, mailers, i18n, gems, config, tests, security, performance and more. Every answer comes from your app.
|
|
66
|
-
- **Prism AST parsing** for model introspection.
|
|
66
|
+
- **Prism AST parsing** for model introspection. A model heading and each scope carry `[VERIFIED]` or `[INFERRED]` (`[STATIC]` without a boot) so the assistant knows what is ground truth and what needs a runtime check.
|
|
67
67
|
- **Three ways in**: MCP over stdio, MCP mounted inside your Rails app over HTTP, or plain CLI in any terminal.
|
|
68
68
|
- **Generated context files** for Claude Code, Cursor, GitHub Copilot, OpenCode and Codex CLI, with the MCP config each tool auto-detects on project open.
|
|
69
69
|
- **Live resources**: `rails://` and `rails-ai-context://` URIs that introspect fresh on every read.
|
|
@@ -87,7 +87,7 @@ bundle add rails-ai-context --group development
|
|
|
87
87
|
rails generate rails_ai_context:install
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
The generator asks which AI tools you use and what to write, then creates the context files, the MCP config for each tool,
|
|
90
|
+
The generator asks which AI tools you use and what to write, then creates the context files, the MCP config for each tool, `config/initializers/rails_ai_context.rb` and `.rails-ai-context.yml`. In a git repo with no pre-commit hook it also offers to install one. An empty answer to the tools question selects all five, and `--defaults` takes the default for each question. Re-running it is safe; it keeps what you have and adds what is missing. Every step is in [What the install generator does](docs/GUIDE.md#what-the-install-generator-does).
|
|
91
91
|
|
|
92
92
|
Keeping your own `CLAUDE.md` and `AGENTS.md`? `rails generate rails_ai_context:install --mcp-only` writes the MCP config and leaves every context file alone.
|
|
93
93
|
|
|
@@ -125,10 +125,12 @@ Then open the project in your AI tool. The MCP config it wrote is picked up on o
|
|
|
125
125
|
The default. Each AI tool gets its own config file (`.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `opencode.json`, `.codex/config.toml`) pointing at:
|
|
126
126
|
|
|
127
127
|
```bash
|
|
128
|
-
rails
|
|
129
|
-
rails-ai-context serve
|
|
128
|
+
bundle exec rails-ai-context serve # in-Gemfile
|
|
129
|
+
rails-ai-context serve # standalone
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
`rails ai:serve` starts the same server from rake.
|
|
133
|
+
|
|
132
134
|
### MCP over HTTP
|
|
133
135
|
|
|
134
136
|
Mount the server inside your app. It inherits your routing, auth and middleware, and needs no second process.
|
|
@@ -152,7 +154,7 @@ rails 'ai:tool[search_code]' pattern="publishable?" match_type=trace
|
|
|
152
154
|
rails-ai-context tool schema --table users --detail full
|
|
153
155
|
```
|
|
154
156
|
|
|
155
|
-
Tool names resolve loosely: `schema`, `get_schema` and `rails_get_schema` all work.
|
|
157
|
+
Tool names resolve loosely: `schema`, `get_schema` and `rails_get_schema` all work. About half the tools take `detail=summary|standard|full`.
|
|
156
158
|
|
|
157
159
|
### Commands
|
|
158
160
|
|
|
@@ -186,9 +188,9 @@ Every tool is read-only and answers from your app.
|
|
|
186
188
|
|
|
187
189
|
A few worth knowing on day one:
|
|
188
190
|
|
|
189
|
-
- `search_code` with `match_type=trace` returns definition, source, every caller grouped by type, and the tests, in one call. That replaces 4 to 5 file reads.
|
|
191
|
+
- `search_code` with `match_type=trace` returns definition, source, every caller grouped by file and labelled by type, and the tests, in one call. That replaces 4 to 5 file reads.
|
|
190
192
|
- `get_controllers` returns the action source with inherited filters, strong params and the render map.
|
|
191
|
-
- `get_model_details` returns associations, validations, scopes, enums and macros from the AST, each tagged `[VERIFIED]` or `[INFERRED]`.
|
|
193
|
+
- `get_model_details` returns associations, validations, scopes, enums and macros from the AST, with the model and each scope tagged `[VERIFIED]` or `[INFERRED]`.
|
|
192
194
|
- `query` runs read-only SQL with a timeout, a row limit and column redaction. `read_logs` redacts sensitive data before it leaves the process.
|
|
193
195
|
|
|
194
196
|
Parameters for all 45 are in the [tools reference](docs/TOOLS.md); worked examples in [recipes](docs/RECIPES.md).
|
|
@@ -234,7 +236,7 @@ Code is found in the conventional layout, in packwerk packs (`packs/*/app/*`), i
|
|
|
234
236
|
|
|
235
237
|
```ruby
|
|
236
238
|
# config/initializers/rails_ai_context.rb
|
|
237
|
-
if defined?(RailsAiContext)
|
|
239
|
+
if defined?(RailsAiContext) && RailsAiContext.respond_to?(:configure)
|
|
238
240
|
RailsAiContext.configure do |config|
|
|
239
241
|
config.ai_tools = %i[claude cursor] # which AI tools to generate for
|
|
240
242
|
config.tool_mode = :mcp # :mcp (default) or :cli
|
|
@@ -254,8 +256,9 @@ Register your own tools next to the built-in ones:
|
|
|
254
256
|
class RailsGetBusinessMetrics < MCP::Tool
|
|
255
257
|
tool_name "rails_get_business_metrics"
|
|
256
258
|
description "Key business metrics for this app"
|
|
259
|
+
input_schema(properties: { period: { type: "string" } })
|
|
257
260
|
|
|
258
|
-
def call(period: "week")
|
|
261
|
+
def self.call(period: "week", server_context: nil)
|
|
259
262
|
MCP::Tool::Response.new([{ type: "text", text: "Users this #{period}: #{User.recent.count}" }])
|
|
260
263
|
end
|
|
261
264
|
end
|
data/SECURITY.md
CHANGED
|
@@ -21,11 +21,11 @@ If you discover a security vulnerability in rails-ai-context, please report it r
|
|
|
21
21
|
- All 45 MCP tools are **read-only** and never modify your application or database.
|
|
22
22
|
- **Sensitive file blocking** - configurable `sensitive_patterns` blocks access to `.env`, `*.key`, `*.pem`, `credentials.yml.enc` across all search and read tools. Patterns are checked in `rails_search_code`, `rails_get_edit_context`, and all new tools.
|
|
23
23
|
- **Path traversal protection** - all file-reading tools validate paths with `File.realpath()` against `Rails.root` to prevent directory escape.
|
|
24
|
-
- **Command injection prevention** - code search uses `Open3.
|
|
25
|
-
- **Regex DoS protection** - user-supplied regex patterns have 1-2 second timeouts via `Regexp.new(pattern, timeout:)
|
|
26
|
-
- **Credential safety** - `rails_get_env` only reads `.env.example` (never `.env`), shows credential key names only (never values), and redacts secrets. `rails_get_config` exposes adapter/framework names, not connection strings.
|
|
24
|
+
- **Command injection prevention** - code search uses `Open3.capture3` with array arguments (never shell strings). The `--` flag separator prevents pattern injection.
|
|
25
|
+
- **Regex DoS protection** - user-supplied regex patterns have 1-2 second timeouts via `Regexp.new(pattern, timeout:)` on Ruby 3.2 and newer. Ruby 3.1 has no `Regexp.timeout`, so the timeout is skipped there.
|
|
26
|
+
- **Credential safety** - `rails_get_env` only reads `.env.example`, `.env.sample` or `.env.template` (never `.env`), shows credential key names only (never values), and redacts secrets. `rails_get_config` exposes adapter/framework names, not connection strings.
|
|
27
27
|
- **Brakeman integration** - optional `rails_security_scan` tool runs static security analysis. Graceful degradation if not installed. Users can exclude it via `config.skip_tools = %w[rails_security_scan]`.
|
|
28
|
-
- **File size limits** -
|
|
28
|
+
- **File size limits** - tools that read a whole file skip one over the configurable `max_file_size` (default 5MB) to prevent memory exhaustion on large files. `rails_read_logs` reads the last 1MB of a log instead, and `rails_search_code` caps its output with `max_search_results`.
|
|
29
29
|
- **SQL comment stripping** - `rails_query` strips block (`/* */`), line (`--`), and MySQL-style (`#`) comments before validation to prevent keyword hiding.
|
|
30
30
|
- **Regex interpolation safety** - all introspectors use `Regexp.escape` when interpolating model/association names into patterns to prevent regex injection.
|
|
31
31
|
- **Log redaction** - `rails_read_logs` redacts passwords, tokens, secrets, API keys, cookies, session IDs, emails, and environment variables before output.
|
data/docs/ARCHITECTURE.md
CHANGED
|
@@ -93,7 +93,7 @@ flowchart LR
|
|
|
93
93
|
F --> G[Cache Result]
|
|
94
94
|
G --> D
|
|
95
95
|
D --> H[Hydrate\nSchema Hints]
|
|
96
|
-
H --> I[Truncate\
|
|
96
|
+
H --> I[Truncate\nmax_tool_response_chars]
|
|
97
97
|
I --> J[MCP::Tool::Response]
|
|
98
98
|
```
|
|
99
99
|
|
|
@@ -132,10 +132,10 @@ Two more answer a question a tool asks:
|
|
|
132
132
|
|
|
133
133
|
**Prism AST parsing** replaced all regex-based Ruby source parsing in v5.2.0.
|
|
134
134
|
|
|
135
|
-
- **AstCache** - Thread-safe parse cache (`Concurrent::Map`), keyed by
|
|
135
|
+
- **AstCache** - Thread-safe parse cache (`Concurrent::Map`), keyed by the SHA256 of the file's content; a stat match answers without a read for a file already two seconds older than the read that recorded it
|
|
136
136
|
- **SourceIntrospector** - Single-pass Prism Dispatcher walks the AST once, feeding every registered listener simultaneously
|
|
137
|
-
- **Listeners** - Associations, Validations, Scopes, Enums, Callbacks, Macros and
|
|
138
|
-
- **Confidence** -
|
|
137
|
+
- **Listeners** - Associations, Validations, Scopes, Enums, Callbacks, Macros, Methods and Mixins are the default map for model analysis; the rest are used through targeted walks over schema dumps, migrations, Gemfiles, rake tasks and initializers, and `MethodCallListener` reports a named call with its arguments and options wherever one is asked for
|
|
138
|
+
- **Confidence** - A listener that records one tags each record `[VERIFIED]` (static literals) or `[INFERRED]` (dynamic expressions), and a record in a static-tier entry is capped at `[STATIC]`, since no record can claim more than the tier that carries it
|
|
139
139
|
|
|
140
140
|
### Tool Registry (`lib/rails_ai_context/tools/base_tool.rb`)
|
|
141
141
|
|
|
@@ -196,7 +196,7 @@ Result: controller and view tools automatically include relevant schema informat
|
|
|
196
196
|
| `CopilotSerializer` | `.github/copilot-instructions.md` |
|
|
197
197
|
| `CopilotInstructionsSerializer` | `.github/instructions/*.instructions.md` |
|
|
198
198
|
| `OpencodeSerializer` | `AGENTS.md` (root) |
|
|
199
|
-
| `OpencodeRulesSerializer` | `app
|
|
199
|
+
| `OpencodeRulesSerializer` | `app/models/AGENTS.md`, `app/controllers/AGENTS.md` |
|
|
200
200
|
| `JsonSerializer` | `.ai-context.json` |
|
|
201
201
|
| `MarkdownSerializer` | Base formatting |
|
|
202
202
|
| `ContextFileSerializer` | Atomic file writes with section markers |
|
|
@@ -207,6 +207,7 @@ Result: controller and view tools automatically include relevant schema informat
|
|
|
207
207
|
| `SectionFacts` | The facts every surface states about an app - auth, assets, associations, the filter chain, an unread entry's row, the static-tier notice - each rendered in one place |
|
|
208
208
|
| `SectionMarkerWriter` | Writes a managed section into a file the user also owns |
|
|
209
209
|
| `ContextModeDispatch` | Picks full or compact rendering for a run |
|
|
210
|
+
| `Base` | Holds the context hash a serializer renders |
|
|
210
211
|
|
|
211
212
|
### CLI (`exe/rails-ai-context`, `lib/rails_ai_context/cli/`)
|
|
212
213
|
|
|
@@ -219,7 +220,7 @@ Thor-based CLI that works standalone (no Gemfile entry):
|
|
|
219
220
|
|
|
220
221
|
### Path safety (`lib/rails_ai_context/safe_path.rb`, `lib/rails_ai_context/view_file.rb`)
|
|
221
222
|
|
|
222
|
-
- **SafePath** - Resolves a caller-supplied path once, in a fixed refusal order: traversal, sensitive name, realpath, containment, sensitive realpath, file, size. `safe_glob` is the globbed-path form
|
|
223
|
+
- **SafePath** - Resolves a caller-supplied path once, in a fixed refusal order: traversal, sensitive name, realpath, containment, sensitive realpath, file, size. `BaseTool.safe_glob` is the globbed-path form, checking realpath, containment and the sensitive realpath on each path a pattern yields
|
|
223
224
|
- **ViewFile** - Which file a view name means, and whether the app owns it, so no tool builds a template path itself
|
|
224
225
|
|
|
225
226
|
### Caching
|
|
@@ -237,7 +238,7 @@ Four cache layers:
|
|
|
237
238
|
|
|
238
239
|
SHA256-based change detection:
|
|
239
240
|
|
|
240
|
-
- Watches: `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`, `spec/`, Gemfile.lock, `config.ru`, the Rakefile
|
|
241
|
+
- Watches: `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`, `spec/`, the Gemfile and Gemfile.lock (or `gems.rb` and `gems.locked`), `package.json`, `tsconfig.json`, `config.ru`, the Rakefile
|
|
241
242
|
- Computes a composite fingerprint from all watched files
|
|
242
243
|
- Used by introspection cache and live reload to detect actual changes
|
|
243
244
|
|
|
@@ -245,9 +246,9 @@ SHA256-based change detection:
|
|
|
245
246
|
|
|
246
247
|
1. **Official MCP SDK** - Not a custom protocol. Uses `mcp` gem's `MCP::Tool`, `MCP::Server`, transports.
|
|
247
248
|
2. **Read-only tools** - All 45 tools annotated as non-destructive. Defense-in-depth for query tool.
|
|
248
|
-
3. **Graceful degradation** - Works without database (parses schema.rb
|
|
249
|
-
4. **Zeitwerk autoloading** - Files loaded on-demand.
|
|
250
|
-
5. **Diff-aware generation** - Context file regeneration skips unchanged
|
|
249
|
+
3. **Graceful degradation** - Works without database (parses `schema.rb` or `structure.sql`, or replays the migrations), without Brakeman, without ripgrep, without listen gem.
|
|
250
|
+
4. **Zeitwerk autoloading** - Files loaded on-demand. `require_relative` is kept for the few files that load before or outside the loader.
|
|
251
|
+
5. **Diff-aware generation** - Context file regeneration skips a file whose content is unchanged, the timestamp line aside.
|
|
251
252
|
6. **Section markers** - Root file content wrapped in `<!-- BEGIN/END rails-ai-context -->` to preserve user-added content.
|
|
252
253
|
|
|
253
254
|
## Dependencies
|
data/docs/CLI.md
CHANGED
|
@@ -29,6 +29,7 @@ Start the MCP server.
|
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
rails ai:serve # stdio (default)
|
|
32
|
+
rails ai:serve_http # HTTP transport
|
|
32
33
|
rails-ai-context serve # stdio (default)
|
|
33
34
|
rails-ai-context serve --transport http --port 6029 # HTTP transport
|
|
34
35
|
```
|
|
@@ -58,7 +59,8 @@ rails-ai-context tool model_details --model User
|
|
|
58
59
|
| Option | Description |
|
|
59
60
|
|:-------|:------------|
|
|
60
61
|
| `--list` | List all available tools |
|
|
61
|
-
| `--json` | Output as JSON |
|
|
62
|
+
| `--json` | Output as JSON (`-j` after the tool name) |
|
|
63
|
+
| `--help` | After the tool name, print that tool's parameters. Rake: `help=true` |
|
|
62
64
|
| `--no-boot` | Skip booting the app; answer from source alone |
|
|
63
65
|
|
|
64
66
|
Global options may be typed before or after the command name:
|
|
@@ -94,11 +96,15 @@ unanswered.
|
|
|
94
96
|
|
|
95
97
|
A thing that is simply not there is an ordinary answer and exits 0: a file the
|
|
96
98
|
tool looked for and did not find, a search with no matches, a directory the
|
|
97
|
-
`--path` names that does not exist.
|
|
99
|
+
`--path` names that does not exist. A tool that needs a
|
|
100
|
+
booted app and is run with `--no-boot` (`query`, `runtime_info`) prints
|
|
101
|
+
`[UNAVAILABLE]` with the reason and also exits 0.
|
|
98
102
|
|
|
99
103
|
A value the schema cannot hold - `--detail bogus` where the parameter takes
|
|
100
104
|
`summary`, `standard` or `full`, or `--limit abc` where it takes an integer -
|
|
101
|
-
is warned about on stderr and the tool's own default is used.
|
|
105
|
+
is warned about on stderr and the tool's own default is used. A required
|
|
106
|
+
parameter has no default, so a value outside its list (`--action bogus` on
|
|
107
|
+
`migration_advisor`) exits 1.
|
|
102
108
|
|
|
103
109
|
### The static tier and `--no-boot`
|
|
104
110
|
|
|
@@ -131,7 +137,7 @@ rails 'ai:tool[get_schema]'
|
|
|
131
137
|
rails 'ai:tool[rails_get_schema]'
|
|
132
138
|
```
|
|
133
139
|
|
|
134
|
-
Resolution order: exact match → `rails_` prefix → `rails_get_` prefix → `
|
|
140
|
+
Resolution order: exact match → `rails_` prefix → `rails_get_` prefix → short name (the name with `rails_get_` or `rails_` removed).
|
|
135
141
|
|
|
136
142
|
### `context`
|
|
137
143
|
|
|
@@ -143,7 +149,9 @@ rails ai:context:claude # Claude only
|
|
|
143
149
|
rails ai:context:cursor # Cursor only
|
|
144
150
|
rails ai:context:copilot # Copilot only
|
|
145
151
|
rails ai:context:opencode # OpenCode only
|
|
152
|
+
rails ai:context:codex # Codex only
|
|
146
153
|
rails ai:context:json # JSON export
|
|
154
|
+
rails ai:context:full # All formats, full mode
|
|
147
155
|
|
|
148
156
|
rails-ai-context context # All
|
|
149
157
|
rails-ai-context context --format claude # Specific format
|
|
@@ -153,6 +161,10 @@ rails-ai-context context --format claude # Specific format
|
|
|
153
161
|
|:-------|:--------|:------------|
|
|
154
162
|
| `--format` | all configured | `claude`, `cursor`, `copilot`, `opencode`, `codex`, `json`, `all` |
|
|
155
163
|
|
|
164
|
+
The rake tasks `ai:context`, `ai:context:<format>` and `ai:context_for` also read
|
|
165
|
+
`CONTEXT_MODE`: `CONTEXT_MODE=full rails ai:context` overrides `config.context_mode`
|
|
166
|
+
for that run. The standalone binary does not read it.
|
|
167
|
+
|
|
156
168
|
### `doctor`
|
|
157
169
|
|
|
158
170
|
Run the diagnostic checks and report an AI readiness score.
|
|
@@ -183,14 +195,16 @@ Interactive setup for standalone mode.
|
|
|
183
195
|
|
|
184
196
|
```bash
|
|
185
197
|
rails-ai-context init
|
|
198
|
+
rails-ai-context init --mcp-only # MCP config only, no context files
|
|
186
199
|
```
|
|
187
200
|
|
|
188
|
-
Asks which AI tools to configure and
|
|
201
|
+
Asks which AI tools to configure and what to write: MCP config and context files, context files only (CLI mode), or MCP config only. Creates `.rails-ai-context.yml`, the MCP config files (except in CLI mode) and the context files (except with MCP config only). `--mcp-only` skips the second question.
|
|
189
202
|
|
|
190
203
|
### `version`
|
|
191
204
|
|
|
192
205
|
```bash
|
|
193
206
|
rails-ai-context version
|
|
207
|
+
rails-ai-context --version # or -v
|
|
194
208
|
```
|
|
195
209
|
|
|
196
210
|
### `inspect`
|
|
@@ -199,6 +213,7 @@ Print introspection summary as JSON.
|
|
|
199
213
|
|
|
200
214
|
```bash
|
|
201
215
|
rails-ai-context inspect
|
|
216
|
+
rails ai:inspect # short text summary instead of JSON
|
|
202
217
|
```
|
|
203
218
|
|
|
204
219
|
### `facts`
|
|
@@ -206,17 +221,19 @@ rails-ai-context inspect
|
|
|
206
221
|
Print a schema facts summary: tables, associations and dependencies.
|
|
207
222
|
|
|
208
223
|
```bash
|
|
224
|
+
rails ai:facts
|
|
209
225
|
rails-ai-context facts
|
|
210
226
|
rails-ai-context facts --no-boot
|
|
211
227
|
```
|
|
212
228
|
|
|
213
229
|
### `preset`
|
|
214
230
|
|
|
215
|
-
Run a named group of tools in one pass
|
|
231
|
+
Run a named group of tools in one pass: `architecture`, `debugging` or `migration`. With no name it lists the presets.
|
|
216
232
|
|
|
217
233
|
```bash
|
|
218
234
|
rails-ai-context preset
|
|
219
235
|
rails-ai-context preset architecture
|
|
236
|
+
rails 'ai:preset[architecture]'
|
|
220
237
|
```
|
|
221
238
|
|
|
222
239
|
### `tree`
|
|
@@ -239,12 +256,18 @@ rails-ai-context tree
|
|
|
239
256
|
| `rails ai:context:cursor` | Generate Cursor context |
|
|
240
257
|
| `rails ai:context:copilot` | Generate Copilot context |
|
|
241
258
|
| `rails ai:context:opencode` | Generate OpenCode context |
|
|
259
|
+
| `rails ai:context:codex` | Generate Codex context |
|
|
242
260
|
| `rails ai:context:json` | Generate JSON export |
|
|
261
|
+
| `rails ai:context:full` | Generate all formats in full mode |
|
|
262
|
+
| `rails 'ai:context_for[claude]'` | Generate one format by name |
|
|
243
263
|
| `rails ai:serve` | Start MCP server (stdio) |
|
|
264
|
+
| `rails ai:serve_http` | Start MCP server (HTTP) |
|
|
244
265
|
| `rails ai:tool` | List tools or run a tool |
|
|
245
266
|
| `rails ai:doctor` | Run diagnostics |
|
|
246
267
|
| `rails ai:watch` | Watch mode |
|
|
247
|
-
| `rails ai:
|
|
268
|
+
| `rails ai:inspect` | Print introspection summary |
|
|
269
|
+
| `rails ai:facts` | Print schema facts summary |
|
|
270
|
+
| `rails 'ai:preset[name]'` | Run a preset; no name lists them |
|
|
248
271
|
|
|
249
272
|
---
|
|
250
273
|
|
|
@@ -259,6 +282,7 @@ rails 'ai:tool[tool_name]' key=value key2=value2
|
|
|
259
282
|
- Strings: `table=users`
|
|
260
283
|
- Booleans: `explain=true` or `explain=false`
|
|
261
284
|
- Enums: `detail=full`
|
|
285
|
+
- Arrays: `files=a.rb,b.rb`
|
|
262
286
|
- Spaces: `pattern="has_many :posts"`
|
|
263
287
|
|
|
264
288
|
### Thor format
|
|
@@ -270,6 +294,7 @@ rails-ai-context tool tool_name --key value --key2 value2
|
|
|
270
294
|
- Strings: `--table users` or `--table=users`
|
|
271
295
|
- Booleans: `--explain` (true) or `--no-explain` (false)
|
|
272
296
|
- Enums: `--detail full`
|
|
297
|
+
- Arrays: `--files a.rb b.rb` or `--files a.rb,b.rb`
|
|
273
298
|
- Spaces: `--pattern "has_many :posts"`
|
|
274
299
|
|
|
275
300
|
### JSON output
|
|
@@ -278,6 +303,7 @@ Add `--json` for machine-readable output:
|
|
|
278
303
|
|
|
279
304
|
```bash
|
|
280
305
|
rails-ai-context tool schema --table users --json
|
|
306
|
+
JSON=1 rails 'ai:tool[schema]' table=users
|
|
281
307
|
```
|
|
282
308
|
|
|
283
309
|
---
|