rails-ai-bridge 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/GEMINI.md +55 -0
- data/README.md +80 -1
- data/docs/roadmap-context-assistants.md +4 -2
- data/docs/roadmaps.md +12 -2
- data/lib/generators/rails_ai_bridge/install/install_generator.rb +2 -0
- data/lib/rails_ai_bridge/assistant_formats_preference.rb +1 -1
- data/lib/rails_ai_bridge/serializers/context_file_serializer.rb +3 -1
- data/lib/rails_ai_bridge/serializers/formatters/gemini_footer_formatter.rb +33 -0
- data/lib/rails_ai_bridge/serializers/formatters/gemini_header_formatter.rb +29 -0
- data/lib/rails_ai_bridge/serializers/providers/base_provider_serializer.rb +251 -0
- data/lib/rails_ai_bridge/serializers/providers/claude_rules_serializer.rb +135 -128
- data/lib/rails_ai_bridge/serializers/providers/claude_serializer.rb +17 -204
- data/lib/rails_ai_bridge/serializers/providers/codex_serializer.rb +87 -106
- data/lib/rails_ai_bridge/serializers/providers/codex_support_serializer.rb +41 -39
- data/lib/rails_ai_bridge/serializers/providers/copilot_instructions_serializer.rb +133 -124
- data/lib/rails_ai_bridge/serializers/providers/copilot_serializer.rb +122 -139
- data/lib/rails_ai_bridge/serializers/providers/cursor_rules_serializer.rb +216 -213
- data/lib/rails_ai_bridge/serializers/providers/gemini_serializer.rb +30 -0
- data/lib/rails_ai_bridge/serializers/providers/rules_serializer.rb +102 -125
- data/lib/rails_ai_bridge/serializers/providers/windsurf_rules_serializer.rb +71 -63
- data/lib/rails_ai_bridge/serializers/providers/windsurf_serializer.rb +99 -86
- data/lib/rails_ai_bridge/tasks/rails_ai_bridge.rake +2 -1
- data/lib/rails_ai_bridge/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 179af8f51240a13daa27c526299db931aaac585186cb55b4f1ca440c24cd66a8
|
|
4
|
+
data.tar.gz: 295dd64afcefc38f247612020fd9933246a1948123ca785293e5f2350bb39b58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 98b696d49e6dfd9fffbac01d4cda299b2e0d518eea998bb703dafb71e5577b7d21e9f1024b6d126907808281f48f05cdb73b80c442a72c950c5a8db046198ab5
|
|
7
|
+
data.tar.gz: a0590ccdc592b161e289313d45f2820b9b793661201b318f68f89c8c4a3535c849d6422327879edd60b7e7a8c14356ab9ea35bcfb4ca9cb6caecf0fac908247d
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.1.0] - 2026-04-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Gemini Support:** Added support for Google's Gemini AI assistant via `GEMINI.md`.
|
|
15
|
+
- **New Rake Task:** Added `rails ai:bridge:gemini` to generate Gemini-specific context.
|
|
16
|
+
- **Context Harmonization:** Refactored all provider serializers (Claude, Gemini, Codex, Copilot, Cursor, Windsurf) to use a shared `BaseProviderSerializer`.
|
|
17
|
+
- **Enhanced AI Guidance:** All context files now feature directive headers, complexity-sorted model lists, and explicit behavioral rules to improve AI code generation.
|
|
18
|
+
- **Improved Metadata:** Context files now include descriptions for key config files and standard maintenance commands (e.g., `rubocop`).
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Internal Refactor:** Extracted common rendering logic into `RailsAiBridge::Serializers::Providers::BaseProviderSerializer` to ensure consistency and maintainability across all AI assistants.
|
|
23
|
+
|
|
10
24
|
## [2.0.0] - 2026-03-31
|
|
11
25
|
|
|
12
26
|
### Added
|
data/GEMINI.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# GEMINI.md — rails-ai-bridge development guide
|
|
2
|
+
|
|
3
|
+
This is a Ruby gem that auto-introspects Rails applications and exposes their
|
|
4
|
+
structure to AI assistants via the Model Context Protocol (MCP).
|
|
5
|
+
|
|
6
|
+
## Architecture
|
|
7
|
+
|
|
8
|
+
- `lib/rails_ai_bridge.rb` — Main entry point, public API (Zeitwerk autoloaded)
|
|
9
|
+
- `lib/rails_ai_bridge/configuration.rb` — User-facing config with presets (:standard, :full)
|
|
10
|
+
- `lib/rails_ai_bridge/introspector.rb` — Orchestrates sub-introspectors
|
|
11
|
+
- `lib/rails_ai_bridge/introspectors/` — 27 introspectors (schema, models, routes, jobs, gems, conventions, stimulus, database_stats, controllers, views, turbo, i18n, config, active_storage, action_text, auth, api, tests, rake_tasks, assets, devops, action_mailbox, migrations, seeds, middleware, engines, multi_database)
|
|
12
|
+
- `lib/rails_ai_bridge/tools/` — 9 MCP tools using the official mcp SDK
|
|
13
|
+
- `lib/rails_ai_bridge/serializers/` — Output formatters (claude, claude_rules, cursor_rules, windsurf, windsurf_rules, copilot, copilot_instructions, rules, markdown, JSON, gemini)
|
|
14
|
+
- `lib/rails_ai_bridge/resources.rb` — MCP resources (static data AI clients read directly)
|
|
15
|
+
- `lib/rails_ai_bridge/server.rb` — MCP server configuration (stdio + HTTP transports)
|
|
16
|
+
- `lib/rails_ai_bridge/middleware.rb` — Rack middleware for auto-mounting MCP HTTP endpoint
|
|
17
|
+
- `lib/rails_ai_bridge/fingerprinter.rb` — SHA256 file fingerprinting for cache invalidation
|
|
18
|
+
- `lib/rails_ai_bridge/doctor.rb` — Diagnostic checks and AI readiness scoring
|
|
19
|
+
- `lib/rails_ai_bridge/watcher.rb` — File watcher for auto-regenerating context files
|
|
20
|
+
- `lib/rails_ai_bridge/engine.rb` — Rails Engine for auto-integration
|
|
21
|
+
- `lib/generators/rails_ai_bridge/install/` — Install generator (creates .mcp.json, initializer, context files)
|
|
22
|
+
|
|
23
|
+
## Key Design Decisions
|
|
24
|
+
|
|
25
|
+
1. **Built on official mcp SDK** — not a custom protocol implementation
|
|
26
|
+
2. **Zero-config** — Railtie auto-registers at boot, introspects without setup
|
|
27
|
+
3. **Graceful degradation** — works without DB by parsing schema.rb as text
|
|
28
|
+
4. **Read-only tools only** — all MCP tools are annotated as non-destructive
|
|
29
|
+
5. **Dual output** — static files (GEMINI.md) + live MCP server (stdio/HTTP)
|
|
30
|
+
6. **Diff-aware** — context regeneration skips unchanged files
|
|
31
|
+
7. **Per-assistant serializers** — each AI tool gets tailored output format
|
|
32
|
+
8. **Zeitwerk autoloading** — files loaded on-demand, not all upfront
|
|
33
|
+
9. **Introspector presets** — `:standard` (9 core) default, `:full` (26) for power users
|
|
34
|
+
10. **MCP auto-discovery** — `.mcp.json` generated by install generator
|
|
35
|
+
11. **Compact by default** — context files ≤150 lines, MCP tools use `detail` parameter (summary/standard/full)
|
|
36
|
+
12. **Per-tool split rules** — `.claude/rules/`, `.cursor/rules/`, `.windsurf/rules/`, `.github/instructions/`
|
|
37
|
+
|
|
38
|
+
## Testing
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bundle exec rspec # Run specs (364 examples)
|
|
42
|
+
bundle exec rubocop # Lint
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Uses combustion gem for testing Rails engine behavior in isolation.
|
|
46
|
+
|
|
47
|
+
## Conventions
|
|
48
|
+
|
|
49
|
+
- Ruby 3.2+ features OK (pattern matching, etc.)
|
|
50
|
+
- Follow rubocop-rails-omakase style
|
|
51
|
+
- Every introspector returns a Hash, never raises (wraps errors in `{ error: msg }`)
|
|
52
|
+
- MCP tools return `MCP::Tool::Response` objects per SDK convention
|
|
53
|
+
- All tools prefixed with `rails_` per MCP naming best practices
|
|
54
|
+
- `generate_context` returns `{ written: [], skipped: [] }` hash
|
|
55
|
+
- Zeitwerk autoloads all files — no `require_relative` needed for new classes
|
data/README.md
CHANGED
|
@@ -32,7 +32,7 @@ flowchart LR
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
1. **Up to 27 introspectors** scan schema, models, routes, controllers, jobs, gems, conventions, and more (preset `:standard` runs 9 core ones by default; `:full` runs all).
|
|
35
|
-
2. **`rails ai:bridge`** writes bounded bridge files for Claude, Cursor, Copilot, Codex, Windsurf, and JSON.
|
|
35
|
+
2. **`rails ai:bridge`** writes bounded bridge files for Claude, Cursor, Copilot, Codex, Windsurf, Gemini, and JSON.
|
|
36
36
|
3. **`rails ai:serve`** exposes **9 MCP tools** so assistants pull detail on demand (`detail: "summary"` first, then drill down).
|
|
37
37
|
|
|
38
38
|
### Folder guides
|
|
@@ -301,6 +301,84 @@ Codex support is centered on **`AGENTS.md`** at the repository root.
|
|
|
301
301
|
|
|
302
302
|
---
|
|
303
303
|
|
|
304
|
+
## Best Practices
|
|
305
|
+
|
|
306
|
+
After testing with Cursor, Windsurf, Copilot, Codex, and Claude Code in real projects, these patterns consistently produce the best results.
|
|
307
|
+
|
|
308
|
+
### Layer 1: Commit your static files
|
|
309
|
+
|
|
310
|
+
The generated files (`.cursorrules`, `.cursor/rules/`, `AGENTS.md`, `.windsurfrules`, `CLAUDE.md`, `.github/copilot-instructions.md`) are loaded **passively** by AI tools on every session start — giving the assistant immediate project grounding before it reads a single line of your code.
|
|
311
|
+
|
|
312
|
+
**Always commit these files.** The whole team benefits, not just the developer who ran `rails ai:bridge`.
|
|
313
|
+
|
|
314
|
+
### Layer 2: Run the MCP server
|
|
315
|
+
|
|
316
|
+
Static files cover overview. The MCP server covers depth. When an assistant needs full schema details, specific model associations, or a filtered route listing, the `rails_*` tools fetch live data on demand — without inflating your initial context window.
|
|
317
|
+
|
|
318
|
+
The combination is additive:
|
|
319
|
+
|
|
320
|
+
| Setup | What you get |
|
|
321
|
+
|-------|-------------|
|
|
322
|
+
| Static files only | Passive overview: project structure always loaded |
|
|
323
|
+
| MCP server only | On-demand depth: accurate live data, no passive grounding |
|
|
324
|
+
| **Both (recommended)** | **Passive overview + on-demand depth = best coverage** |
|
|
325
|
+
|
|
326
|
+
This is the pattern that consistently outperforms either layer alone. The files reduce orientation overhead; the server handles the details when the assistant actually needs them.
|
|
327
|
+
|
|
328
|
+
### Keep files fresh — regenerate after every significant change
|
|
329
|
+
|
|
330
|
+
Static files are snapshots. An assistant working from a schema that is 20 commits out of date will still make assumptions based on the old structure. After any significant change — a new model, a migration, a refactor, a feature merged — run:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
rails ai:bridge
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Rule of thumb:** treat `rails ai:bridge` the same way you treat `bundle install` after a `Gemfile` change — a routine step, not a one-time setup. Commit the regenerated files alongside the code change so the whole team stays in sync.
|
|
337
|
+
|
|
338
|
+
#### Auto-regeneration during active development
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
rails ai:watch
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Watches for file changes and regenerates relevant context files automatically. Useful when you are actively adding models, routes, or controllers and want the assistant to track along in the same session.
|
|
345
|
+
|
|
346
|
+
### Use `detail: "summary"` first with the MCP server
|
|
347
|
+
|
|
348
|
+
When the MCP server is running, start broad and drill down:
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
1. rails_get_schema(detail: "summary") → all tables, no noise
|
|
352
|
+
2. rails_get_schema(table: "orders") → full detail for one table
|
|
353
|
+
3. rails_get_model_details(model: "Order") → associations, validations, scopes
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
This keeps token usage low and answer quality high. Requesting full detail on every table at once is rarely necessary and wastes context on data the assistant does not need yet.
|
|
357
|
+
|
|
358
|
+
### Pick the right preset for your app
|
|
359
|
+
|
|
360
|
+
| Preset | Introspectors | Best for |
|
|
361
|
+
|--------|--------------|---------|
|
|
362
|
+
| `:standard` (default) | 9 core | Most apps — schema, models, routes, jobs, gems, conventions |
|
|
363
|
+
| `:full` | 27 | Full-stack apps where frontend, auth, API, and DevOps context matter |
|
|
364
|
+
|
|
365
|
+
Add individual introspectors on top of a preset for targeted additions:
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
config.preset = :standard
|
|
369
|
+
config.introspectors += %i[views auth api]
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Check your readiness score
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
rails ai:doctor
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Prints a 0–100 AI readiness score and flags anything missing: `.mcp.json`, generated context files, MCP token in production, and more. Run it after initial setup and after major configuration changes.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
304
382
|
## Configuration
|
|
305
383
|
|
|
306
384
|
```ruby
|
|
@@ -402,6 +480,7 @@ Frontend introspectors (views, Turbo, Stimulus, assets) degrade gracefully — t
|
|
|
402
480
|
| `rails ai:bridge:cursor` | Generate Cursor files only |
|
|
403
481
|
| `rails ai:bridge:windsurf` | Generate Windsurf files only |
|
|
404
482
|
| `rails ai:bridge:copilot` | Generate Copilot files only |
|
|
483
|
+
| `rails ai:bridge:gemini` | Generate Gemini files only |
|
|
405
484
|
| `rails ai:serve` | Start MCP server (stdio) |
|
|
406
485
|
| `rails ai:serve_http` | Start MCP server (HTTP) |
|
|
407
486
|
| `rails ai:doctor` | Run diagnostics and AI readiness score (0-100) |
|
|
@@ -8,13 +8,15 @@ This track is **separate** from [roadmap-mcp-v2.md](roadmap-mcp-v2.md) (MCP HTTP
|
|
|
8
8
|
|
|
9
9
|
- Sharpen per-assistant formats (structure, length, cross-links) based on real usage feedback.
|
|
10
10
|
- Reduce duplication and noise while keeping "always-on" rules discoverable.
|
|
11
|
-
|
|
11
|
+
## Done
|
|
12
|
+
|
|
12
13
|
- Separate LLM provider serializers from domain infrastructure (done: `Serializers::Providers::` namespace).
|
|
13
14
|
- DRY the formatter hierarchy (done: `SectionFormatter` template method base).
|
|
15
|
+
- Align tool references and workflow hints across Claude, Cursor, Copilot, Windsurf, Codex, and **Gemini** (v2.1.0).
|
|
16
|
+
- Refactor provider serializers with a shared `BaseProviderSerializer` for consistent, high-fidelity output (v2.1.0).
|
|
14
17
|
|
|
15
18
|
## In progress
|
|
16
19
|
|
|
17
|
-
- Refining per-provider serializer output (context quality improvements on `pr5-context-quality` branch)
|
|
18
20
|
- Custom Rails directory introspection coverage gaps
|
|
19
21
|
|
|
20
22
|
## Relation to versioning
|
data/docs/roadmaps.md
CHANGED
|
@@ -33,8 +33,18 @@ Entry point to see **which tracks exist** and **what is left**. Details live in
|
|
|
33
33
|
| `Mcp::Authenticator` consolidation | Done |
|
|
34
34
|
| Provider serializers extracted to `Serializers::Providers::` | Done |
|
|
35
35
|
| `SectionFormatter` template method base (DRY guard pattern) | Done |
|
|
36
|
-
|
|
|
37
|
-
|
|
36
|
+
| Major release (2.0.0) | Done |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## v2.1.0 — Gemini & Harmonization
|
|
41
|
+
|
|
42
|
+
| Area | Status |
|
|
43
|
+
|------|--------|
|
|
44
|
+
| Gemini Support (`GEMINI.md`, `GeminiSerializer`, Rake task) | Done |
|
|
45
|
+
| Context Harmonization (Shared `BaseProviderSerializer`) | Done |
|
|
46
|
+
| Enhanced directive guidance for all assistants | Done |
|
|
47
|
+
| Release (2.1.0) | Done |
|
|
38
48
|
|
|
39
49
|
---
|
|
40
50
|
|
|
@@ -168,6 +168,7 @@ module RailsAiBridge
|
|
|
168
168
|
say " rails ai:bridge:claude # Generate CLAUDE.md only"
|
|
169
169
|
say " rails ai:bridge:codex # Generate AGENTS.md only"
|
|
170
170
|
say " rails ai:bridge:cursor # Generate .cursorrules only"
|
|
171
|
+
say " rails ai:bridge:gemini # Generate GEMINI.md only"
|
|
171
172
|
say " rails ai:serve # Start MCP server (stdio)"
|
|
172
173
|
say " rails ai:inspect # Print introspection summary"
|
|
173
174
|
say ""
|
|
@@ -177,6 +178,7 @@ module RailsAiBridge
|
|
|
177
178
|
say " Cursor → .cursorrules + .cursor/rules/*.mdc (incl. rails-engineering.mdc)"
|
|
178
179
|
say " Windsurf → .windsurfrules + .windsurf/rules/*.md"
|
|
179
180
|
say " GitHub Copilot → .github/copilot-instructions.md + .github/instructions/*.instructions.md"
|
|
181
|
+
say " Gemini → GEMINI.md"
|
|
180
182
|
say ""
|
|
181
183
|
say "MCP auto-discovery:", :yellow
|
|
182
184
|
say " .mcp.json is auto-detected by Claude Code and Cursor."
|
|
@@ -20,7 +20,7 @@ module RailsAiBridge
|
|
|
20
20
|
RELATIVE_PATH = "config/rails_ai_bridge/install.yml"
|
|
21
21
|
|
|
22
22
|
# All recognized format keys (order is not significant).
|
|
23
|
-
FORMAT_KEYS = %i[claude codex cursor windsurf copilot json].freeze
|
|
23
|
+
FORMAT_KEYS = %i[claude codex cursor windsurf copilot json gemini].freeze
|
|
24
24
|
|
|
25
25
|
class << self
|
|
26
26
|
# Absolute path to the preference file for the current Rails application.
|
|
@@ -14,7 +14,8 @@ module RailsAiBridge
|
|
|
14
14
|
cursor: ".cursorrules",
|
|
15
15
|
windsurf: ".windsurfrules",
|
|
16
16
|
copilot: ".github/copilot-instructions.md",
|
|
17
|
-
json: ".ai-context.json"
|
|
17
|
+
json: ".ai-context.json",
|
|
18
|
+
gemini: "GEMINI.md"
|
|
18
19
|
}.freeze
|
|
19
20
|
|
|
20
21
|
def initialize(context, format: :all)
|
|
@@ -67,6 +68,7 @@ module RailsAiBridge
|
|
|
67
68
|
when :codex then Providers::CodexSerializer.new(context).call
|
|
68
69
|
when :cursor then Providers::RulesSerializer.new(context).call
|
|
69
70
|
when :windsurf then Providers::WindsurfSerializer.new(context).call
|
|
71
|
+
when :gemini then Providers::GeminiSerializer.new(context).call
|
|
70
72
|
when :copilot then Providers::CopilotSerializer.new(context).call
|
|
71
73
|
else MarkdownSerializer.new(context).call
|
|
72
74
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAiBridge
|
|
4
|
+
module Serializers
|
|
5
|
+
module Formatters
|
|
6
|
+
# Renders the Gemini footer with behavioral rules and regeneration note.
|
|
7
|
+
class GeminiFooterFormatter < Base
|
|
8
|
+
# Renders the footer for the GEMINI.md file.
|
|
9
|
+
#
|
|
10
|
+
# @return [String] The rendered footer.
|
|
11
|
+
def call
|
|
12
|
+
arch = context.dig(:conventions, :architecture)
|
|
13
|
+
arch_summary = arch&.any? ? arch.join(", ") : nil
|
|
14
|
+
|
|
15
|
+
lines = [
|
|
16
|
+
"## Behavioral Rules",
|
|
17
|
+
"",
|
|
18
|
+
"- **Adhere to Conventions:** Strictly follow the existing patterns and conventions outlined in this document.",
|
|
19
|
+
"- **Schema as Source of Truth:** Always use the database schema as the definitive source for column names, types, and relationships.",
|
|
20
|
+
"- **Respect Existing Logic:** Ensure all new code respects existing associations, validations, and service objects.",
|
|
21
|
+
"- **Write Tests:** All new features and bug fixes must be accompanied by corresponding tests."
|
|
22
|
+
]
|
|
23
|
+
lines << "- **Match Architecture:** Align with the project's architectural style (#{arch_summary})." if arch_summary
|
|
24
|
+
lines << "- **Verify Correctness:** Run `#{ContextSummary.test_command(context)}` and `bundle exec rubocop` after making changes to ensure correctness and style adherence."
|
|
25
|
+
lines << ""
|
|
26
|
+
lines << "---"
|
|
27
|
+
lines << "_This context file is auto-generated. Run `rails ai:bridge` to regenerate._"
|
|
28
|
+
lines.join("\n")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAiBridge
|
|
4
|
+
module Serializers
|
|
5
|
+
module Formatters
|
|
6
|
+
# Renders the Gemini-specific document header.
|
|
7
|
+
class GeminiHeaderFormatter < Base
|
|
8
|
+
# Renders the header for the GEMINI.md file.
|
|
9
|
+
#
|
|
10
|
+
# @return [String] The rendered header.
|
|
11
|
+
def call
|
|
12
|
+
<<~MD
|
|
13
|
+
# #{context[:app_name]} — AI Context
|
|
14
|
+
|
|
15
|
+
> Auto-generated by rails-ai-bridge v#{RailsAiBridge::VERSION}
|
|
16
|
+
> Generated: #{context[:generated_at]}
|
|
17
|
+
> Rails #{context[:rails_version]} | Ruby #{context[:ruby_version]}
|
|
18
|
+
|
|
19
|
+
This file provides a high-level overview of this Rails application's
|
|
20
|
+
structure, patterns, and conventions. As an AI assistant, use this context
|
|
21
|
+
to quickly understand the project and generate idiomatic code that
|
|
22
|
+
adheres to its design decisions. For deeper dives, use the live
|
|
23
|
+
MCP tools referenced throughout this document.
|
|
24
|
+
MD
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAiBridge
|
|
4
|
+
module Serializers
|
|
5
|
+
module Providers
|
|
6
|
+
# Base class for AI assistant provider serializers (Claude, Copilot, Gemini, Codex, Cursor, Windsurf).
|
|
7
|
+
# Shared compact-mode sections: header, stack, models, gems, architecture, MCP guide, commands, footer.
|
|
8
|
+
class BaseProviderSerializer
|
|
9
|
+
attr_reader :context, :config
|
|
10
|
+
|
|
11
|
+
# @param context [Hash] Introspection hash from {Introspector#call}.
|
|
12
|
+
# @param config [RailsAiBridge::Configuration] Bridge configuration.
|
|
13
|
+
def initialize(context, config: RailsAiBridge.configuration)
|
|
14
|
+
@context = context
|
|
15
|
+
@config = config
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Renders the default compact AI context document (newline-joined sections).
|
|
19
|
+
# Enforces {RailsAiBridge::Configuration#claude_max_lines} by trimming with an MCP pointer when exceeded.
|
|
20
|
+
# Subclasses may override entirely or compose with individual `#render_*` helpers.
|
|
21
|
+
#
|
|
22
|
+
# @return [String] Compact markdown body.
|
|
23
|
+
def render_compact
|
|
24
|
+
lines = []
|
|
25
|
+
lines.concat(render_header)
|
|
26
|
+
lines.concat(render_stack_overview)
|
|
27
|
+
lines.concat(render_key_models)
|
|
28
|
+
lines.concat(render_notable_gems)
|
|
29
|
+
lines.concat(render_architecture)
|
|
30
|
+
lines.concat(render_key_considerations)
|
|
31
|
+
lines.concat(Formatters::McpGuideFormatter.new(context).call.split("\n"))
|
|
32
|
+
lines.concat(render_key_config_files)
|
|
33
|
+
lines.concat(render_commands)
|
|
34
|
+
lines.concat(render_footer)
|
|
35
|
+
|
|
36
|
+
# Enforce max lines (config.claude_max_lines is a generic max line setting)
|
|
37
|
+
max = @config.claude_max_lines
|
|
38
|
+
if lines.size > max
|
|
39
|
+
lines = lines.first(max - 2)
|
|
40
|
+
lines << ""
|
|
41
|
+
lines << "_Context trimmed. Use MCP tools for full details._"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
lines.join("\n")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Renders the header section of the context file.
|
|
48
|
+
# @return [Array<String>] Lines for the header.
|
|
49
|
+
def render_header
|
|
50
|
+
[
|
|
51
|
+
"# #{context[:app_name]} — AI Context",
|
|
52
|
+
"",
|
|
53
|
+
"> Auto-generated by rails-ai-bridge v#{RailsAiBridge::VERSION}",
|
|
54
|
+
"> Generated: #{context[:generated_at]}",
|
|
55
|
+
"> Rails #{context[:rails_version]} | Ruby #{context[:ruby_version]}",
|
|
56
|
+
"",
|
|
57
|
+
"This file provides a high-level overview of this Rails application's",
|
|
58
|
+
"structure, patterns, and conventions. As an AI assistant, use this context",
|
|
59
|
+
"to quickly understand the project and generate idiomatic code that",
|
|
60
|
+
"adheres to its design decisions. For deeper dives, use the live",
|
|
61
|
+
"MCP tools referenced throughout this document."
|
|
62
|
+
]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Renders the stack overview section.
|
|
66
|
+
# @return [Array<String>] Lines for the stack overview.
|
|
67
|
+
def render_stack_overview
|
|
68
|
+
lines = [ "## Stack" ]
|
|
69
|
+
|
|
70
|
+
schema = context[:schema]
|
|
71
|
+
if schema && !schema[:error]
|
|
72
|
+
lines << "- Database: #{schema[:adapter]} — #{schema[:total_tables]} tables"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
models = context[:models]
|
|
76
|
+
if models.is_a?(Hash) && !models[:error]
|
|
77
|
+
lines << "- Models: #{models.size}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
line = ContextSummary.routes_stack_line(context)
|
|
81
|
+
lines << line if line
|
|
82
|
+
|
|
83
|
+
auth = context[:auth]
|
|
84
|
+
if auth.is_a?(Hash) && !auth[:error]
|
|
85
|
+
parts = []
|
|
86
|
+
parts << "Devise" if auth.dig(:authentication, :devise)&.any?
|
|
87
|
+
parts << "Rails 8 auth" if auth.dig(:authentication, :rails_auth)
|
|
88
|
+
parts << "Pundit" if auth.dig(:authorization, :pundit)&.any?
|
|
89
|
+
parts << "CanCanCan" if auth.dig(:authorization, :cancancan)
|
|
90
|
+
lines << "- Auth: #{parts.join(' + ')}" if parts.any?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
jobs = context[:jobs]
|
|
94
|
+
if jobs.is_a?(Hash) && !jobs[:error]
|
|
95
|
+
job_count = jobs[:jobs]&.size || 0
|
|
96
|
+
mailer_count = jobs[:mailers]&.size || 0
|
|
97
|
+
channel_count = jobs[:channels]&.size || 0
|
|
98
|
+
parts = []
|
|
99
|
+
parts << "#{job_count} jobs" if job_count > 0
|
|
100
|
+
parts << "#{mailer_count} mailers" if mailer_count > 0
|
|
101
|
+
parts << "#{channel_count} channels" if channel_count > 0
|
|
102
|
+
lines << "- Async: #{parts.join(', ')}" if parts.any?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
migrations = context[:migrations]
|
|
106
|
+
if migrations.is_a?(Hash) && !migrations[:error]
|
|
107
|
+
pending = migrations[:pending]
|
|
108
|
+
lines << "- Migrations: #{migrations[:total]} total, #{pending&.size || 0} pending"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
lines << ""
|
|
112
|
+
lines
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Renders the key models section.
|
|
116
|
+
# @return [Array<String>] Lines for the key models.
|
|
117
|
+
def render_key_models
|
|
118
|
+
models = context[:models]
|
|
119
|
+
return [] unless models.is_a?(Hash) && !models[:error] && models.any?
|
|
120
|
+
|
|
121
|
+
schema_tables = context.dig(:schema, :tables) || {}
|
|
122
|
+
migrations = context[:migrations]
|
|
123
|
+
max_show = 15
|
|
124
|
+
|
|
125
|
+
lines = [ "## Key Models", "The following are the most architecturally significant models, ordered by complexity:" ]
|
|
126
|
+
sorted_names = models.sort_by { |_name, data| -ContextSummary.model_complexity_score(data) }.map(&:first)
|
|
127
|
+
sorted_names.first(max_show).each do |name|
|
|
128
|
+
data = models[name]
|
|
129
|
+
assoc_count = (data[:associations] || []).size
|
|
130
|
+
val_count = (data[:validations] || []).size
|
|
131
|
+
enum_names = (data[:enums] || {}).keys
|
|
132
|
+
top_assocs = (data[:associations] || []).first(3).map { |a| "#{a[:type]} :#{a[:name]}" }.join(", ")
|
|
133
|
+
table_name = data[:table_name]
|
|
134
|
+
|
|
135
|
+
line = "- **#{name}**"
|
|
136
|
+
line += " (#{assoc_count}a, #{val_count}v)" if assoc_count > 0 || val_count > 0
|
|
137
|
+
line += " [enums: #{enum_names.join(', ')}]" if enum_names.any?
|
|
138
|
+
|
|
139
|
+
cols = ContextSummary.top_columns(schema_tables[table_name])
|
|
140
|
+
line += " [cols: #{cols.map { |c| "#{c[:name]}:#{c[:type]}" }.join(', ')}]" if cols.any?
|
|
141
|
+
|
|
142
|
+
line += " [recently migrated]" if table_name && ContextSummary.recently_migrated?(table_name, migrations)
|
|
143
|
+
line += " — #{top_assocs}" if top_assocs && !top_assocs.empty?
|
|
144
|
+
lines << line
|
|
145
|
+
end
|
|
146
|
+
lines << "- _...#{models.size - max_show} more (use `rails_get_model_details` tool)_" if models.size > max_show
|
|
147
|
+
lines << ""
|
|
148
|
+
lines
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Renders the notable gems section.
|
|
152
|
+
# @return [Array<String>] Lines for the notable gems.
|
|
153
|
+
def render_notable_gems
|
|
154
|
+
gems = context[:gems]
|
|
155
|
+
return [] unless gems.is_a?(Hash) && !gems[:error]
|
|
156
|
+
notable = gems[:notable_gems] || gems[:notable] || gems[:detected] || []
|
|
157
|
+
return [] if notable.empty?
|
|
158
|
+
|
|
159
|
+
lines = [ "## Gems", "Key gems, categorized by their primary function:" ]
|
|
160
|
+
grouped = notable.group_by { |g| g[:category]&.to_s || "other" }
|
|
161
|
+
grouped.each do |category, gem_list|
|
|
162
|
+
names = gem_list.map { |g| g[:name] }.join(", ")
|
|
163
|
+
lines << "- **#{category}**: #{names}"
|
|
164
|
+
end
|
|
165
|
+
lines << ""
|
|
166
|
+
lines
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Renders the architecture section.
|
|
170
|
+
# @return [Array<String>] Lines for the architecture.
|
|
171
|
+
def render_architecture
|
|
172
|
+
conv = context[:conventions]
|
|
173
|
+
return [] unless conv.is_a?(Hash) && !conv[:error]
|
|
174
|
+
|
|
175
|
+
arch = conv[:architecture] || []
|
|
176
|
+
patterns = conv[:patterns] || []
|
|
177
|
+
return [] if arch.empty? && patterns.empty?
|
|
178
|
+
|
|
179
|
+
lines = [ "## Architecture", "Detected architectural styles and common patterns:" ]
|
|
180
|
+
arch.each { |p| lines << "- #{p}" }
|
|
181
|
+
patterns.first(8).each { |p| lines << "- #{p}" }
|
|
182
|
+
lines << ""
|
|
183
|
+
lines
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Renders the key considerations for performance and security.
|
|
187
|
+
# @return [Array<String>] Lines for performance and security considerations.
|
|
188
|
+
def render_key_considerations
|
|
189
|
+
[
|
|
190
|
+
"## Key Considerations",
|
|
191
|
+
"- **Performance:** For large or frequently accessed tables, always consider database performance. Use the `rails_get_schema` tool to verify indexes and be mindful of N+1 queries by using `includes` and other ActiveRecord optimizations.",
|
|
192
|
+
"- **Security:** Treat all user-provided input as untrusted. Always use strong parameters in controllers and be aware of potential security vulnerabilities when using gems like `ransack` or `pg_search`.",
|
|
193
|
+
"- **Data Drift:** This document is a snapshot. For the most up-to-date information, especially regarding schema and routes, use the live MCP tools.",
|
|
194
|
+
"- **MCP Exposure:** The MCP tools are read-only but expose sensitive application structure. Avoid exposing the HTTP transport on untrusted networks.",
|
|
195
|
+
""
|
|
196
|
+
]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Renders the key configuration files section.
|
|
200
|
+
# @return [Array<String>] Lines for the key config files.
|
|
201
|
+
def render_key_config_files
|
|
202
|
+
conv = context[:conventions]
|
|
203
|
+
return [] unless conv.is_a?(Hash) && !conv[:error]
|
|
204
|
+
|
|
205
|
+
config_files = conv[:config_files] || []
|
|
206
|
+
return [] if config_files.empty?
|
|
207
|
+
|
|
208
|
+
lines = [ "## Key Config Files", "Core configuration files for this application:" ]
|
|
209
|
+
config_files.first(5).each { |f| lines << "- `#{f}`" }
|
|
210
|
+
lines << ""
|
|
211
|
+
lines
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Renders the command reference section.
|
|
215
|
+
# @return [Array<String>] Lines for the commands.
|
|
216
|
+
def render_commands
|
|
217
|
+
[
|
|
218
|
+
"## Commands",
|
|
219
|
+
"- `bin/dev` — start dev server",
|
|
220
|
+
"- `#{ContextSummary.test_command(context)}` — run tests",
|
|
221
|
+
"- `bundle exec rubocop` — run linter",
|
|
222
|
+
"- `rails db:migrate` — run pending migrations",
|
|
223
|
+
""
|
|
224
|
+
]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Renders the closing rules and regeneration footer.
|
|
228
|
+
# @return [Array<String>] Lines for the rules section and trailing attribution.
|
|
229
|
+
def render_footer
|
|
230
|
+
arch = context.dig(:conventions, :architecture)
|
|
231
|
+
arch_summary = arch&.any? ? arch.join(", ") : nil
|
|
232
|
+
|
|
233
|
+
lines = [
|
|
234
|
+
"## Rules",
|
|
235
|
+
"",
|
|
236
|
+
"- **Adhere to Conventions:** Strictly follow the existing patterns and conventions outlined in this document.",
|
|
237
|
+
"- **Schema as Source of Truth:** Always use the database schema as the definitive source for column names, types, and relationships.",
|
|
238
|
+
"- **Respect Existing Logic:** Ensure all new code respects existing associations, validations, and service objects.",
|
|
239
|
+
"- **Write Tests:** All new features and bug fixes must be accompanied by corresponding tests."
|
|
240
|
+
]
|
|
241
|
+
lines << "- **Match Architecture:** Align with the project's architectural style (#{arch_summary})." if arch_summary
|
|
242
|
+
lines << "- **Verify Correctness:** Run `#{ContextSummary.test_command(context)}` and `bundle exec rubocop` after making changes to ensure correctness and style adherence."
|
|
243
|
+
lines << ""
|
|
244
|
+
lines << "---"
|
|
245
|
+
lines << "_This context file is auto-generated. Run `rails ai:bridge` to regenerate._"
|
|
246
|
+
lines
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|