rails-ai-context 0.13.1 → 0.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9eb210617bf9e0de267dc49a092f8a06c26383bd3dc6ad82ecb52ed61ce08aa6
4
- data.tar.gz: '08929a12848a79b58a7e5e896a8a75c1d7068f8c1fe4910d8fc6f7eb8371aeec'
3
+ metadata.gz: f5b745a3b7eec70c71340022f60a7fadacd80cae84f5d4586936b78633677f0e
4
+ data.tar.gz: 5619c0302827cb96ef33175264e83f36fa2f055925e6517316b379d24599fff7
5
5
  SHA512:
6
- metadata.gz: '0280fc9dff2714ddda17e7cc9f714e3147641bbbbe4a08f01c03515b4a847ac60acf7e68e55d0181ae768f6c8cb7fc1ac0d518b90af5ea390fd2af211d65a34b'
7
- data.tar.gz: 5b5e52eacd2efac4320b633e027af67c5d72aa506c217dd7ddf64d4e969fd471122daeb2d9a0ed5fb4f2b256b65b52509a8785f783e0befb70c15fbea2bcb1c3
6
+ metadata.gz: 810c890604f5d4b78d56e4580254279b3b741d15fd0dbe2419fbf661d2ee47dd70b678cf962aa65244e025e70633dcc5cceee30319c27edf60c04d2a6127ce39
7
+ data.tar.gz: 4f6ed5c100bcf1907b5c38ac9043669ab4c4d833a30c65613d25f0a125de1909f054da48ce3b3fd87f427e1f5e7ff6f8fb422cd812016ed57b85218d592c5f52
data/CLAUDE.md CHANGED
@@ -32,7 +32,7 @@ structure to AI assistants via the Model Context Protocol (MCP).
32
32
  6. **Diff-aware** — context regeneration skips unchanged files
33
33
  7. **Per-assistant serializers** — each AI tool gets tailored output format
34
34
  8. **Zeitwerk autoloading** — files loaded on-demand, not all upfront
35
- 9. **Introspector presets** — `:standard` (12 core) default, `:full` (28) for power users
35
+ 9. **Introspector presets** — `:standard` (13 core) default, `:full` (28) for power users
36
36
  10. **MCP auto-discovery** — `.mcp.json` generated by install generator
37
37
  11. **Compact by default** — context files ≤150 lines, MCP tools use `detail` parameter (summary/standard/full)
38
38
  12. **Per-tool split rules** — `.claude/rules/`, `.cursor/rules/`, `.windsurf/rules/`, `.github/instructions/`
data/README.md CHANGED
@@ -203,7 +203,7 @@ Root files (CLAUDE.md, AGENTS.md, etc.) use **section markers** — your custom
203
203
  | **DevOps** | Puma, Procfile, Docker, deployment tools, asset pipeline |
204
204
  | **Architecture** | Service objects, STI, polymorphism, state machines, multi-tenancy, engines |
205
205
 
206
- 29 introspectors total. The `:standard` preset runs 12 core ones by default; use `:full` for 28 (`database_stats` is opt-in, PostgreSQL only).
206
+ 29 introspectors total. The `:standard` preset runs 13 core ones by default; use `:full` for 28 (`database_stats` is opt-in, PostgreSQL only).
207
207
 
208
208
  ---
209
209
 
@@ -257,7 +257,7 @@ end
257
257
  ```ruby
258
258
  # config/initializers/rails_ai_context.rb
259
259
  RailsAiContext.configure do |config|
260
- # Presets: :standard (12 introspectors, default) or :full (all 28)
260
+ # Presets: :standard (13 introspectors, default) or :full (all 28)
261
261
  config.preset = :standard
262
262
 
263
263
  # Cherry-pick on top of a preset
@@ -291,7 +291,7 @@ end
291
291
  | Option | Default | Description |
292
292
  |--------|---------|-------------|
293
293
  | `preset` | `:standard` | Introspector preset (`:standard` or `:full`) |
294
- | `introspectors` | 12 core | Array of introspector symbols |
294
+ | `introspectors` | 13 core | Array of introspector symbols |
295
295
  | `context_mode` | `:compact` | `:compact` (≤150 lines) or `:full` (dump everything) |
296
296
  | `claude_max_lines` | `150` | Max lines for CLAUDE.md in compact mode |
297
297
  | `max_tool_response_chars` | `120_000` | Safety cap for MCP tool responses |
data/demo_script.sh CHANGED
@@ -8,7 +8,7 @@ echo 'Fetching gem metadata from https://rubygems.org...'
8
8
  sleep 0.3
9
9
  echo 'Resolving dependencies...'
10
10
  sleep 0.3
11
- echo 'Installing rails-ai-context 0.13.1'
11
+ echo 'Installing rails-ai-context 0.14.0'
12
12
  echo ''
13
13
  sleep 1
14
14
 
data/docs/GUIDE.md CHANGED
@@ -579,7 +579,7 @@ Both transports are **read-only** — they expose the same 13 tools and never mo
579
579
  RailsAiContext.configure do |config|
580
580
  # --- Introspectors ---
581
581
 
582
- # Presets: :standard (12 core, default) or :full (all 28)
582
+ # Presets: :standard (13 core, default) or :full (all 28)
583
583
  config.preset = :standard
584
584
 
585
585
  # Cherry-pick on top of a preset
@@ -636,7 +636,7 @@ end
636
636
  | Option | Type | Default | Description |
637
637
  |--------|------|---------|-------------|
638
638
  | `preset` | Symbol | `:standard` | Introspector preset (`:standard` or `:full`) |
639
- | `introspectors` | Array | 12 core symbols | Which introspectors to run |
639
+ | `introspectors` | Array | 13 core symbols | Which introspectors to run |
640
640
  | `context_mode` | Symbol | `:compact` | `:compact` or `:full` |
641
641
  | `claude_max_lines` | Integer | `150` | Max lines for CLAUDE.md in compact mode |
642
642
  | `max_tool_response_chars` | Integer | `120_000` | Safety cap for MCP tool responses |
@@ -673,7 +673,7 @@ All split rules include an app overview file, so no context is lost when root fi
673
673
 
674
674
  ## Introspectors — Full List
675
675
 
676
- ### Standard preset (12 introspectors)
676
+ ### Standard preset (13 introspectors)
677
677
 
678
678
  These run by default. Fast and cover core Rails structure.
679
679
 
@@ -3,7 +3,7 @@
3
3
  module RailsAiContext
4
4
  class Configuration
5
5
  PRESETS = {
6
- standard: %i[schema models routes jobs gems conventions controllers tests migrations stimulus view_templates design_tokens],
6
+ standard: %i[schema models routes jobs gems conventions controllers tests migrations stimulus view_templates design_tokens config],
7
7
  full: %i[schema models routes jobs gems conventions stimulus controllers views view_templates design_tokens turbo
8
8
  i18n config active_storage action_text auth api tests rake_tasks assets
9
9
  devops action_mailbox migrations seeds middleware engines multi_database]
@@ -26,6 +26,9 @@ module RailsAiContext
26
26
  private
27
27
 
28
28
  def extract_routes
29
+ # Force Rails to reload routes if routes.rb has changed
30
+ app.routes_reloader&.execute_if_updated rescue nil
31
+
29
32
  app.routes.routes.filter_map do |route|
30
33
  next if route.respond_to?(:internal?) && route.internal?
31
34
  next if route.defaults[:controller].blank?
@@ -138,8 +138,13 @@ module RailsAiContext
138
138
  tables[current_table] = { columns: [], indexes: [], foreign_keys: [] }
139
139
  elsif current_table && (match = line.match(/t\.(\w+)\s+"(\w+)"/))
140
140
  tables[current_table][:columns] << { name: match[2], type: match[1] }
141
- elsif (match = line.match(/add_index\s+"(\w+)",\s+\[?"(\w+)"/))
142
- tables[match[1]]&.dig(:indexes)&.push({ columns: match[2] })
141
+ elsif (match = line.match(/add_index\s+"(\w+)",\s+(.+)/))
142
+ table_name = match[1]
143
+ rest = match[2]
144
+ cols = rest.scan(/(?::|\")(\w+)/).flatten
145
+ unique = rest.include?("unique: true")
146
+ idx_name = rest.match(/name:\s*"(\w+)"/)&.send(:[], 1)
147
+ tables[table_name]&.dig(:indexes)&.push({ name: idx_name, columns: cols, unique: unique }.compact) if cols.any?
143
148
  end
144
149
  end
145
150
 
@@ -164,9 +169,9 @@ module RailsAiContext
164
169
  end
165
170
 
166
171
  # Match CREATE INDEX / CREATE UNIQUE INDEX
167
- content.scan(/CREATE (?:UNIQUE )?INDEX (\w+) ON (?:public\.)?(\w+).*?\((.+?)\)/m) do |idx_name, table, cols|
168
- col_list = cols.scan(/\w+/).first
169
- tables[table]&.dig(:indexes)&.push({ name: idx_name, columns: col_list })
172
+ content.scan(/CREATE (UNIQUE )?INDEX (\w+) ON (?:public\.)?(\w+).*?\((.+?)\)/m) do |unique, idx_name, table, cols|
173
+ col_list = cols.scan(/\w+/)
174
+ tables[table]&.dig(:indexes)&.push({ name: idx_name, columns: col_list, unique: !!unique })
170
175
  end
171
176
 
172
177
  # Match ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY (handles multi-line)
@@ -52,10 +52,25 @@ module RailsAiContext
52
52
  end
53
53
 
54
54
  def extract_values(content)
55
- match = content.match(/static\s+values\s*=\s*\{([^}]*)\}/m)
55
+ match = content.match(/static\s+values\s*=\s*\{(.*?)\}/m)
56
56
  return {} unless match
57
57
 
58
- match[1].scan(/(\w+)\s*:\s*(\w+)/).to_h
58
+ body = match[1]
59
+ values = {}
60
+
61
+ # Handle complex format: name: { type: Type, default: val }
62
+ body.scan(/(\w+)\s*:\s*\{([^}]+)\}/).each do |name, inner|
63
+ type = inner.match(/type:\s*(\w+)/)&.send(:[], 1) || "Object"
64
+ default = inner.match(/default:\s*(\S+)/)&.send(:[], 1)&.chomp(",")
65
+ values[name] = default ? "#{type} (default: #{default})" : type
66
+ end
67
+
68
+ # Handle simple format: name: Type (single line or multi-line)
69
+ body.scan(/(\w+)\s*:\s*([A-Z]\w+)/).each do |name, type|
70
+ values[name] ||= type
71
+ end
72
+
73
+ values
59
74
  end
60
75
 
61
76
  def extract_actions(content)
@@ -135,10 +135,15 @@ module RailsAiContext
135
135
  end
136
136
  end
137
137
 
138
- # Concerns
138
+ # Concerns — filter out internal Rails modules
139
139
  if data[:concerns]&.any?
140
- lines << "" << "## Concerns"
141
- lines << data[:concerns].map { |c| "- #{c}" }.join("\n")
140
+ app_concerns = data[:concerns].reject do |c|
141
+ c.match?(/\A(ActiveRecord|ActiveModel|ActiveSupport|ActionText|ActionMailbox|ActiveStorage|GeneratedAssociationMethods|Kernel|JSON|PP|Marshal|MessagePack|GeneratedRelationMethods)/)
142
+ end
143
+ if app_concerns.any?
144
+ lines << "" << "## Concerns"
145
+ lines << app_concerns.map { |c| "- #{c}" }.join("\n")
146
+ end
142
147
  end
143
148
 
144
149
  # Key instance methods — include signatures from source if available
@@ -24,13 +24,21 @@ module RailsAiContext
24
24
  offset: {
25
25
  type: "integer",
26
26
  description: "Skip routes for pagination. Default: 0."
27
+ },
28
+ app_only: {
29
+ type: "boolean",
30
+ description: "Filter out internal Rails routes (Active Storage, Action Mailbox, Conductor, etc.). Default: true."
27
31
  }
28
32
  }
29
33
  )
30
34
 
35
+ INTERNAL_PREFIXES = %w[
36
+ action_mailbox/ active_storage/ rails/ conductor/
37
+ ].freeze
38
+
31
39
  annotations(read_only_hint: true, destructive_hint: false, idempotent_hint: true, open_world_hint: false)
32
40
 
33
- def self.call(controller: nil, detail: "standard", limit: nil, offset: 0, server_context: nil)
41
+ def self.call(controller: nil, detail: "standard", limit: nil, offset: 0, app_only: true, server_context: nil)
34
42
  routes = cached_context[:routes]
35
43
  return text_response("Route introspection not available. Add :routes to introspectors.") unless routes
36
44
  return text_response("Route introspection failed: #{routes[:error]}") if routes[:error]
@@ -38,6 +46,11 @@ module RailsAiContext
38
46
  by_controller = routes[:by_controller] || {}
39
47
  offset = [ offset.to_i, 0 ].max
40
48
 
49
+ # Filter out internal Rails routes by default
50
+ if app_only
51
+ by_controller = by_controller.reject { |k, _| INTERNAL_PREFIXES.any? { |p| k.downcase.start_with?(p) } }
52
+ end
53
+
41
54
  # Filter by controller
42
55
  if controller
43
56
  filtered = by_controller.select { |k, _| k.downcase.include?(controller.downcase) }
@@ -70,7 +83,8 @@ module RailsAiContext
70
83
  count += 1
71
84
  next if count <= offset
72
85
  break if count > offset + limit
73
- ctrl_lines << "- `#{r[:verb]}` `#{r[:path]}` #{r[:action]}"
86
+ name_part = r[:name] ? " `#{r[:name]}`" : ""
87
+ ctrl_lines << "- `#{r[:verb]}` `#{r[:path]}` → #{r[:action]}#{name_part}"
74
88
  end
75
89
  if ctrl_lines.any?
76
90
  lines << "## #{ctrl}"
@@ -30,10 +30,12 @@ module RailsAiContext
30
30
  controllers = data[:controllers] || []
31
31
  return text_response("No Stimulus controllers found.") if controllers.empty?
32
32
 
33
- # Specific controller
33
+ # Specific controller — accepts both dash and underscore naming
34
+ # (HTML uses data-controller="weekly-chart", file is weekly_chart_controller.js)
34
35
  if controller
35
- ctrl = controllers.find { |c| c[:name]&.downcase == controller.downcase }
36
- return text_response("Controller '#{controller}' not found. Available: #{controllers.map { |c| c[:name] }.sort.join(', ')}") unless ctrl
36
+ normalized = controller.downcase.tr("-", "_")
37
+ ctrl = controllers.find { |c| c[:name]&.downcase&.tr("-", "_") == normalized }
38
+ return text_response("Controller '#{controller}' not found. Available: #{controllers.map { |c| c[:name] }.sort.join(', ')}\n\n_Note: use dashes in HTML (`data-controller=\"my-name\"`) but underscores for lookup (`controller:\"my_name\"`)._") unless ctrl
37
39
  return text_response(format_controller_full(ctrl))
38
40
  end
39
41
 
@@ -78,6 +80,8 @@ module RailsAiContext
78
80
  lines << "- **Outlets:** #{ctrl[:outlets].join(', ')}" if ctrl[:outlets]&.any?
79
81
  lines << "- **Classes:** #{ctrl[:classes].join(', ')}" if ctrl[:classes]&.any?
80
82
  lines << "- **File:** #{ctrl[:file]}" if ctrl[:file]
83
+ html_name = ctrl[:name]&.tr("_", "-")
84
+ lines << "" << "_HTML: `data-controller=\"#{html_name}\"`_" if html_name
81
85
  lines.join("\n")
82
86
  end
83
87
  end
@@ -27,6 +27,10 @@ module RailsAiContext
27
27
  max_results: {
28
28
  type: "integer",
29
29
  description: "Maximum number of results. Default: 30, max: 100."
30
+ },
31
+ context_lines: {
32
+ type: "integer",
33
+ description: "Lines of context before and after each match (like grep -C). Default: 0, max: 5."
30
34
  }
31
35
  },
32
36
  required: [ "pattern" ]
@@ -34,7 +38,7 @@ module RailsAiContext
34
38
 
35
39
  annotations(read_only_hint: true, destructive_hint: false, idempotent_hint: true, open_world_hint: false)
36
40
 
37
- def self.call(pattern:, path: nil, file_type: nil, max_results: 30, server_context: nil)
41
+ def self.call(pattern:, path: nil, file_type: nil, max_results: 30, context_lines: 0, server_context: nil)
38
42
  root = Rails.root.to_s
39
43
 
40
44
  # Validate file_type to prevent injection
@@ -42,9 +46,10 @@ module RailsAiContext
42
46
  return text_response("Invalid file_type: must contain only alphanumeric characters.")
43
47
  end
44
48
 
45
- # Cap max_results
49
+ # Cap max_results and context_lines
46
50
  max_results = [ max_results.to_i, MAX_RESULTS_CAP ].min
47
51
  max_results = 30 if max_results < 1
52
+ context_lines = [ [ context_lines.to_i, 0 ].max, 5 ].min
48
53
 
49
54
  search_path = path ? File.join(root, path) : root
50
55
 
@@ -64,7 +69,7 @@ module RailsAiContext
64
69
  end
65
70
 
66
71
  results = if ripgrep_available?
67
- search_with_ripgrep(pattern, search_path, file_type, max_results, root)
72
+ search_with_ripgrep(pattern, search_path, file_type, max_results, root, context_lines)
68
73
  else
69
74
  search_with_ruby(pattern, search_path, file_type, max_results, root)
70
75
  end
@@ -84,8 +89,9 @@ module RailsAiContext
84
89
  @rg_available ||= system("which rg > /dev/null 2>&1")
85
90
  end
86
91
 
87
- private_class_method def self.search_with_ripgrep(pattern, search_path, file_type, max_results, root)
92
+ private_class_method def self.search_with_ripgrep(pattern, search_path, file_type, max_results, root, ctx_lines = 0)
88
93
  cmd = [ "rg", "--no-heading", "--line-number", "--max-count", max_results.to_s ]
94
+ cmd.push("-C", ctx_lines.to_s) if ctx_lines > 0
89
95
 
90
96
  RailsAiContext.configuration.excluded_paths.each do |p|
91
97
  cmd << "--glob=!#{p}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsAiContext
4
- VERSION = "0.13.1"
4
+ VERSION = "0.14.0"
5
5
  end
data/server.json CHANGED
@@ -7,11 +7,11 @@
7
7
  "url": "https://github.com/crisnahine/rails-ai-context",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.13.1",
10
+ "version": "0.14.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "mcpb",
14
- "identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v0.13.1/rails-ai-context-mcp.mcpb",
14
+ "identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v0.14.0/rails-ai-context-mcp.mcpb",
15
15
  "fileSha256": "dd711a0ad6c4de943ae4da94eaf59a6dc9494b9d57f726e24649ed4e2f156990",
16
16
  "transport": {
17
17
  "type": "stdio"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-ai-context
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - crisnahine