Class: RailsAiContext::LiveReload
- Inherits:
-
Object
- Object
- RailsAiContext::LiveReload
- Defined in:
- lib/rails_ai_context/live_reload.rb
Overview
Watches for file changes and automatically invalidates MCP tool caches, sending notifications to connected AI clients so they re-query fresh data. Runs a background thread alongside the MCP server (stdio or HTTP).
Constant Summary collapse
- WATCH_DIRS =
(Watcher::WATCH_PATTERNS | Fingerprinter::WATCHED_DIRS).freeze
Instance Attribute Summary collapse
-
#app ⇒ Object
readonly
Returns the value of attribute app.
-
#mcp_server ⇒ Object
readonly
Returns the value of attribute mcp_server.
Instance Method Summary collapse
-
#categorize_changes(paths) ⇒ Object
Group changed file paths by category (model, controller, etc.).
-
#format_change_message(categories) ⇒ Object
Build a readable summary like “Files changed: 2 model(s), 1 controller(s).”.
-
#handle_change(changed_paths = []) ⇒ Object
Process a batch of file changes.
-
#initialize(app, mcp_server) ⇒ LiveReload
constructor
A new instance of LiveReload.
-
#start ⇒ Object
Start the file watcher in a background thread.
-
#stop ⇒ Object
Stop the background listener thread.
Constructor Details
#initialize(app, mcp_server) ⇒ LiveReload
Returns a new instance of LiveReload.
12 13 14 15 16 |
# File 'lib/rails_ai_context/live_reload.rb', line 12 def initialize(app, mcp_server) @app = app @mcp_server = mcp_server @last_fingerprint = Fingerprinter.compute(app) end |
Instance Attribute Details
#app ⇒ Object (readonly)
Returns the value of attribute app.
10 11 12 |
# File 'lib/rails_ai_context/live_reload.rb', line 10 def app @app end |
#mcp_server ⇒ Object (readonly)
Returns the value of attribute mcp_server.
10 11 12 |
# File 'lib/rails_ai_context/live_reload.rb', line 10 def mcp_server @mcp_server end |
Instance Method Details
#categorize_changes(paths) ⇒ Object
Group changed file paths by category (model, controller, etc.)
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/rails_ai_context/live_reload.rb', line 76 def categorize_changes(paths) categories = Hash.new(0) paths.each do |path| category = case path when %r{app/models} then "model" when %r{app/controllers} then "controller" when %r{app/views} then "view" when %r{app/jobs} then "job" when %r{app/mailers} then "mailer" when %r{app/javascript} then "javascript" when %r{config/routes} then "route" when %r{config/} then "config" when %r{db/migrate} then "migration" when %r{db/} then "database" when %r{lib/tasks} then "rake_task" else "file" end categories[category] += 1 end categories end |
#format_change_message(categories) ⇒ Object
Build a readable summary like “Files changed: 2 model(s), 1 controller(s).”
102 103 104 105 |
# File 'lib/rails_ai_context/live_reload.rb', line 102 def (categories) parts = categories.map { |cat, count| "#{count} #{cat}(s)" } "Files changed: #{parts.join(", ")}." end |
#handle_change(changed_paths = []) ⇒ Object
Process a batch of file changes. Public for testability.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/rails_ai_context/live_reload.rb', line 51 def handle_change(changed_paths = []) return unless Fingerprinter.changed?(app, @last_fingerprint) @last_fingerprint = Fingerprinter.compute(app) # Invalidate all tool caches (includes AstCache.clear) Tools::BaseTool.reset_all_caches! # Build a human-readable change summary = (categorize_changes(changed_paths)) # Notify connected MCP clients mcp_server.notify_resources_list_changed mcp_server.( data: "#{} Tool caches invalidated.", level: "info", logger: "rails-ai-context" ) $stderr.puts "[rails-ai-context] #{} Tool caches invalidated." rescue => e $stderr.puts "[rails-ai-context] Live reload error: #{e.}" end |
#start ⇒ Object
Start the file watcher in a background thread. Non-blocking.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/rails_ai_context/live_reload.rb', line 19 def start require "listen" root = app.root.to_s debounce = RailsAiContext.configuration.live_reload_debounce dirs = WATCH_DIRS.map { |p| File.join(root, p) }.select { |d| Dir.exist?(d) } if dirs.empty? $stderr.puts "[rails-ai-context] Live reload: no watchable directories found" return end $stderr.puts "[rails-ai-context] Live reload enabled (debounce: #{debounce}s)" $stderr.puts "[rails-ai-context] Watching: #{dirs.map { |d| d.sub("#{root}/", "") }.join(", ")}" listener = Listen.to(*dirs, wait_for_delay: debounce) do |modified, added, removed| all_changes = modified + added + removed next if all_changes.empty? handle_change(all_changes) end listener.start @listener = listener end |
#stop ⇒ Object
Stop the background listener thread.
46 47 48 |
# File 'lib/rails_ai_context/live_reload.rb', line 46 def stop @listener&.stop end |