rails-ai-context 5.36.3 → 5.36.4

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: ecc8cfb0b751983a682c2a2c0bd2082216595b83f12f679d190506425d60ae48
4
- data.tar.gz: e0674fed2e5f40ebe3f33166c59d9c17bcd5c5160352100cf8b38d87a73387a4
3
+ metadata.gz: d0dd31bd07cbfa7f5cb1e1232627cda1c7ea16f035f864d8db86e1f544bb3893
4
+ data.tar.gz: 92512c007d7fc751a59bf72b1f651fb2a6e58117bc47697e109c145cbd65b6cf
5
5
  SHA512:
6
- metadata.gz: c78d50c7e050632c88f60e901a8805ed5d397f2f12fb9412fa7ac64af1ed3921ff776486a8e4912f0a5a531e6a90661ca6d7b35def872bc4cd11215bde2941b0
7
- data.tar.gz: 0b597f6cc7372007cb84936ac62f2ca893791622c62a667ea4601b545c73a9719949f3abfc09fd8fe75fe2e143fb1bfa5a4bbf82ee47e4f5fca6d293392c5b9d
6
+ metadata.gz: 0d751847ce7745680c03f69a54077e1f8a1836c37e788d78ad5072792af68be4f7e531b98e5bc3b62159e8b297259d8ebc2649ee22c41b6150edc90e9faf0c69
7
+ data.tar.gz: a64566fdec0abb014195d48dcf81d2463a24b9c72addaec1254dcac0778e6f4190805baddfdf073ec2ca5b847fe4ebc4ba643de759b46fc5a16e13b586e80dab
data/CHANGELOG.md CHANGED
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.36.4] - 2026-10-16
9
+
10
+ ### Added
11
+
12
+ - **The standalone binary says when a newer release is out.** A copy installed
13
+ with `gem install` stayed at its version until someone thought to update it.
14
+ It now asks rubygems.org for the latest version at most once a day, in a
15
+ detached child process, and keeps the answer under `$XDG_STATE_HOME`
16
+ (`~/.local/state/rails-ai-context/latest`). A command run in a terminal prints
17
+ one line on stderr naming the Ruby to update under and the two commands to
18
+ run. The MCP server adds the line to its instructions and to the end of
19
+ `rails_onboard` for the agent to pass on, or asks for a restart when the new
20
+ version is already installed. `RAILS_AI_CONTEXT_NO_UPDATE_CHECK=1` turns it
21
+ off. It is off in CI, on a prerelease, for an older version run on purpose
22
+ (`_5.30.0_`) and in a copy the app's bundle runs. Nothing is installed for you.
23
+
24
+ ### Fixed
25
+
26
+ - **The code reads prism 2 the way it reads prism 1.** prism's main branch
27
+ (2.0.0.rc1) parses with the running Ruby's grammar when it is given none,
28
+ replaced `MissingNode` with `ErrorRecoveryNode`, and leaves an unterminated
29
+ heredoc without a closing location. The gem now names the newest grammar
30
+ when it parses and lexes, reads either recovery node, and ends an
31
+ unterminated heredoc at its body. The gemspec still requires prism below 2.0.
32
+
8
33
  ## [5.36.3] - 2026-10-15
9
34
 
10
35
  ### Fixed
data/SECURITY.md CHANGED
@@ -32,4 +32,4 @@ If you discover a security vulnerability in rails-ai-context, please report it r
32
32
  - **Migration input validation** - `rails_migration_advisor` validates table and column names as safe identifiers before generating migration code.
33
33
  - **Cache invalidation coverage** - Fingerprinter watches `app/components`, `package.json`, and `tsconfig.json` alongside models/controllers/views to prevent stale tool responses.
34
34
  - **Fetch size limits** - `rails_search_docs` caps fetched documentation content at 2MB to prevent memory exhaustion.
35
- - The gem makes outbound HTTPS requests only when `rails_search_docs` is called with `fetch: true` (to fetch Rails documentation from GitHub raw content). All other tools are offline.
35
+ - The gem makes outbound HTTPS requests in two places. `rails_search_docs` fetches Rails documentation from GitHub raw content when called with `fetch: true`. The standalone binary (installed with `gem install`, not through the app's Gemfile) asks `https://rubygems.org/api/v1/versions/rails-ai-context/latest.json` for the latest version at most once a day, in a child process that sends nothing about the app, and writes the answer to `rails-ai-context/latest` under `$XDG_STATE_HOME` (`~/.local/state`). `RAILS_AI_CONTEXT_NO_UPDATE_CHECK=1` turns it off; it is off in CI and in a copy the app's bundle runs. All other tools are offline.
@@ -340,6 +340,12 @@ Proof sources:
340
340
  with Rails 7.0 and 7.2, Ruby 3.3 with Rails 7.1, Ruby 3.4 with Rails 8.0 and
341
341
  8.1, and Ruby 4.0 with Rails 8.1; and the unit suite and the E2E harness on
342
342
  Linux, macOS and Windows.
343
+ In the v5.36.4 release QA: the update check run as the installed binary on
344
+ Ruby 3.1, 3.3, 3.4 and 4.0 against rubygems.org, through a terminal and over
345
+ MCP stdio on a fixture app, a large private app booted and another in the
346
+ static tier, with the app's pinned json and net-http still loaded; the
347
+ suite on prism 1.4.0 and on prism main under Ruby 3.2 and 3.4; and the unit
348
+ suite and the E2E harness on Linux, macOS and Windows.
343
349
  2. Non-crash coverage for every built-in tool including `get_view` in
344
350
  `spec/e2e/in_gemfile_install_spec.rb`'s full-tool sweep; output correctness
345
351
  (ivar cross-check, render-form detection, partial interfaces) verified
@@ -266,6 +266,7 @@ server entry's `env`.
266
266
  | Variable | Read by | Effect |
267
267
  |:---------|:--------|:-------|
268
268
  | `RAILS_AI_CONTEXT_BOOT_TIMEOUT` | Every command that boots the app, and the `ai:serve`, `ai:serve_http` and `ai:tool` rake tasks | Seconds a boot may take before it counts as failed; a command that can then answers from the static tier. Default 20 for `serve` over stdio and `rails ai:serve`, whose client waits about 30 seconds for its first answer, and 60 for everything else. A value that is not a whole number warns and keeps the default |
269
+ | `RAILS_AI_CONTEXT_NO_UPDATE_CHECK` | The standalone binary | Any non-empty value turns off the daily check for a newer release on rubygems.org. The check also stays off in CI, on a prerelease and in a copy the app's bundle runs |
269
270
  | `RAILS_AI_CONTEXT_SERVER_NAME` | The MCP server | The name it announces for one process, in place of the default `server_name`; a `server_name` the app sets wins. A workspace's MCP entries set it |
270
271
  | `DEBUG` | Every command and the MCP server | Any value prints the backtrace of a failed boot, a failed command and an exception that escapes a tool, and names the errors a fallback swallows |
271
272
  | `RAILS_ENV`, `RACK_ENV` | Every command | The Rails environment to boot, read the way Rails reads it; `--environment` sets it for one command. Default `development` |
data/docs/SECURITY.md CHANGED
@@ -18,7 +18,7 @@
18
18
  1. **Read-only by design** - All 35 tools are annotated as non-destructive in the MCP protocol
19
19
  2. **Defense in depth** - Multiple security layers, not single points of failure
20
20
  3. **Sensitive data blocking** - Configurable patterns prevent access to secrets
21
- 4. **Offline by default** - No network calls except optional `rails_search_docs` with `fetch: true`
21
+ 4. **Offline except where named** - No network calls except optional `rails_search_docs` with `fetch: true`, and the standalone binary's daily version check against rubygems.org (`RAILS_AI_CONTEXT_NO_UPDATE_CHECK=1` turns it off)
22
22
  5. **Graceful degradation** - Missing optional dependencies don't expose errors or state
23
23
 
24
24
  ---
data/docs/STANDALONE.md CHANGED
@@ -140,6 +140,26 @@ Codex CLI is special - it `env_clear()`s the process before spawning MCP servers
140
140
 
141
141
  If you switch Ruby versions, re-run `rails-ai-context init` to update the snapshot.
142
142
 
143
+ ## Updating
144
+
145
+ A gem installed with `gem install` stays at its version until you update it:
146
+
147
+ ```bash
148
+ gem update rails-ai-context
149
+ rails-ai-context init --defaults # in each app: brings its MCP configs and context files up to the new version
150
+ ```
151
+
152
+ Each Ruby has its own copy, so update under the Ruby your AI client starts the
153
+ server with. Restart the AI client afterwards so its MCP server runs the new copy.
154
+
155
+ The binary checks rubygems.org for a newer release at most once a day, in the
156
+ background. When there is one, a command run in a terminal says so on stderr,
157
+ and the MCP server tells the agent to pass it on, in its instructions and at the
158
+ end of `rails_onboard`. A server reads the answer the check left the last time,
159
+ so the first check shows up from the next start. `RAILS_AI_CONTEXT_NO_UPDATE_CHECK=1` turns the check off.
160
+ It stays off in CI and in a copy the app's bundle runs, where the lockfile picks
161
+ the version.
162
+
143
163
  ## Switching between standalone and in-Gemfile
144
164
 
145
165
  You can switch freely:
data/exe/rails-ai-context CHANGED
@@ -201,7 +201,10 @@ class RailsAiContextCLI < Thor
201
201
  # One answer for any command's failure; Thor's refusals and SystemExit pass through.
202
202
  no_commands do
203
203
  def invoke_command(command, *args)
204
- super
204
+ start_update_check
205
+ result = super
206
+ check_for_update
207
+ result
205
208
  rescue Thor::Error
206
209
  raise
207
210
  rescue StandardError, ScriptError => e
@@ -319,6 +322,7 @@ class RailsAiContextCLI < Thor
319
322
  # No Thor default: it would overwrite the config file's http_port.
320
323
  ::RailsAiContext.configuration.http_port = options[:port] if options[:port]
321
324
 
325
+ start_update_check&.refresh(env: STARTING_ENV)
322
326
  ::RailsAiContext.start_mcp_server(transport: transport)
323
327
  end
324
328
 
@@ -1326,6 +1330,29 @@ class RailsAiContextCLI < Thor
1326
1330
  nil
1327
1331
  end
1328
1332
 
1333
+ # A notice on a terminal only: stdout may be piped, and an agent reads rails_onboard's copy instead.
1334
+ def check_for_update
1335
+ check = start_update_check or return
1336
+ check.refresh(env: STARTING_ENV)
1337
+ note = check.notice unless check.told?
1338
+ $stderr.puts note if note && $stderr.tty?
1339
+ rescue StandardError, ScriptError
1340
+ nil
1341
+ end
1342
+
1343
+ # Only this copy run outside a bundle checks; under bundle exec the app's lockfile picks the version.
1344
+ # Started before the command, while Gem.path is still the one an app's boot replaces.
1345
+ def start_update_check
1346
+ return if STARTING_ENV["BUNDLE_BIN_PATH"]
1347
+
1348
+ @start_update_check ||= begin
1349
+ require_relative "../lib/rails_ai_context/update_check"
1350
+ ::RailsAiContext::UpdateCheck.tap(&:enable!)
1351
+ end
1352
+ rescue StandardError, ScriptError
1353
+ nil
1354
+ end
1355
+
1329
1356
  # Stdlib only, so it is safe to load before the app boots.
1330
1357
  def entry_boot
1331
1358
  @entry_boot ||= begin
@@ -80,14 +80,14 @@ module RailsAiContext
80
80
  # A one-shot parse that stores nothing: for a scan reading thousands of
81
81
  # files once, whose entries would only evict what other readers reuse.
82
82
  def self.parse_uncached(source)
83
- Prism.parse(source)
83
+ Prism.parse(source, version: NEWEST_GRAMMAR)
84
84
  end
85
85
 
86
86
  # Whether a file parses under `ruby:`'s grammar, with no tree built or
87
87
  # kept: the check before a loader requires each file it walks.
88
88
  def self.parse_success?(path, ruby: nil)
89
89
  version = prism_version(ruby)
90
- version ? Prism.parse_file_success?(path, version: version) : Prism.parse_file_success?(path)
90
+ Prism.parse_file_success?(path, version: version || NEWEST_GRAMMAR)
91
91
  end
92
92
 
93
93
  # Parse a Ruby source string, cached by content digest, so every extractor
@@ -112,6 +112,8 @@ module RailsAiContext
112
112
  private_class_method :content_key
113
113
 
114
114
  OLDEST_GRAMMAR = "3.3"
115
+ # Named, because prism 2 parses with the running Ruby's grammar when given none.
116
+ NEWEST_GRAMMAR = "latest"
115
117
  GRAMMARS = Concurrent::Map.new # major.minor => whether the loaded prism parses it
116
118
 
117
119
  # The grammar prism parses `ruby` with: its own major.minor, the oldest prism
@@ -134,7 +136,7 @@ module RailsAiContext
134
136
  private_class_method :supported?
135
137
 
136
138
  def self.prism_parse(source, version)
137
- version ? Prism.parse(source, version: version) : Prism.parse(source)
139
+ Prism.parse(source, version: version || NEWEST_GRAMMAR)
138
140
  end
139
141
  private_class_method :prism_parse
140
142
 
@@ -122,7 +122,7 @@ module RailsAiContext
122
122
  end
123
123
 
124
124
  def lex(text)
125
- Prism.lex(text).value.map(&:first)
125
+ Prism.lex(text, version: AstCache::NEWEST_GRAMMAR).value.map(&:first)
126
126
  rescue StandardError
127
127
  []
128
128
  end
@@ -9,6 +9,9 @@ module RailsAiContext
9
9
  # and Que's `self.queue = "name"`, or a `def self.queue` returning one, which
10
10
  # Resque asks for when @queue is unset. A literal is kept as its value, anything else as source.
11
11
  class QueueAssignmentListener < BaseListener
12
+ # What a half-written `@queue =` parses its value as: MissingNode before prism 2, ErrorRecoveryNode from it.
13
+ RECOVERY_NODES = %i[MissingNode ErrorRecoveryNode].filter_map { |name| Prism.const_get(name) if Prism.const_defined?(name, false) }.freeze
14
+
12
15
  def initialize
13
16
  super
14
17
  @def_depth = 0
@@ -40,9 +43,8 @@ module RailsAiContext
40
43
 
41
44
  private
42
45
 
43
- # A half-written `@queue =` parses with a MissingNode value.
44
46
  def record(node, value, form)
45
- return if value.is_a?(Prism::MissingNode)
47
+ return if RECOVERY_NODES.any? { |type| value.is_a?(type) }
46
48
 
47
49
  literal = value.is_a?(Prism::SymbolNode) || value.is_a?(Prism::StringNode)
48
50
  @results << { form: form, queue: literal ? value.unescaped : nil, source: value.slice,
@@ -18,12 +18,12 @@ module RailsAiContext
18
18
  # @return [String] the node's slice, followed by the body of every
19
19
  # heredoc it opens whose body lies past its end
20
20
  def text(node)
21
- trailing = heredocs(node).select { |doc| doc.closing_loc.end_offset > node.location.end_offset }
21
+ trailing = heredocs(node).select { |doc| body_end(doc).end_offset > node.location.end_offset }
22
22
  return node.slice if trailing.empty?
23
23
 
24
24
  first = trailing.min_by { |doc| body_start(doc).start_offset }
25
- last = trailing.max_by { |doc| doc.closing_loc.end_offset }
26
- "#{node.slice}\n#{body_start(first).join(last.closing_loc).slice}"
25
+ last = trailing.max_by { |doc| body_end(doc).end_offset }
26
+ "#{node.slice}\n#{body_start(first).join(body_end(last)).slice}"
27
27
  end
28
28
 
29
29
  def heredocs(node)
@@ -40,9 +40,14 @@ module RailsAiContext
40
40
  def body_start(doc)
41
41
  if doc.respond_to?(:content_loc) then doc.content_loc
42
42
  elsif doc.parts.any? then doc.parts.first.location
43
- else doc.closing_loc
43
+ else body_end(doc)
44
44
  end
45
45
  end
46
+
47
+ # The terminator; an unterminated heredoc has none from prism 2 on, and ends with its body.
48
+ def body_end(doc)
49
+ doc.closing_loc || (doc.content_loc if doc.respond_to?(:content_loc)) || doc.parts.last&.location || doc.location
50
+ end
46
51
  end
47
52
  end
48
53
  end
@@ -273,11 +273,15 @@ module RailsAiContext
273
273
  # code changed (BaseTool.stale_code_note).
274
274
  def self.instructions
275
275
  intro = "Ground truth engine for Rails apps. Live Prism AST introspection."
276
- return "#{intro} Answers follow edits to the app's files." if CodeReloader.reloadable? || RailsAiContext.static_tier?
277
-
278
- "#{intro} Answers follow edits to the app's files, except what reflection reads, such as associations: " \
279
- "RAILS_ENV=#{RailsAiContext.environment_name} does not reload code, so that stays as of boot, and every " \
280
- "answer says when app code changed since."
276
+ text = if CodeReloader.reloadable? || RailsAiContext.static_tier?
277
+ "#{intro} Answers follow edits to the app's files."
278
+ else
279
+ "#{intro} Answers follow edits to the app's files, except what reflection reads, such as associations: " \
280
+ "RAILS_ENV=#{RailsAiContext.environment_name} does not reload code, so that stays as of boot, and every " \
281
+ "answer says when app code changed since."
282
+ end
283
+ note = UpdateCheck.agent_note
284
+ note ? "#{text} #{note}" : text
281
285
  end
282
286
 
283
287
  # Anything that still escapes a tool (schema validation bugs, SDK-level
@@ -46,7 +46,8 @@ module RailsAiContext
46
46
  else
47
47
  compose_standard(ctx)
48
48
  end
49
- text_response(body, suffix: introspection_warnings_note(ctx))
49
+ update = RailsAiContext::UpdateCheck.agent_note
50
+ text_response(body, suffix: [ introspection_warnings_note(ctx), ("\n\n---\n#{update}" if update) ].compact.join)
50
51
  end
51
52
 
52
53
  class << self
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require_relative "version"
5
+
6
+ module RailsAiContext
7
+ # Tells the copy `gem install` put on PATH that rubygems.org has a newer
8
+ # release. Only the gem's own binary, run outside a bundle, turns it on
9
+ # (enable!), so a copy an app's bundle loads never checks: its lockfile
10
+ # decides the version.
11
+ #
12
+ # Core Ruby only. The binary loads this before the app boots, and requiring
13
+ # net/http or json here would activate the newest installed versions over
14
+ # the ones the app's lock pins. The fetch runs in a detached child instead,
15
+ # at most once a day, and the version it writes is what the next check reads.
16
+ module UpdateCheck
17
+ OPT_OUT_ENV = "RAILS_AI_CONTEXT_NO_UPDATE_CHECK"
18
+ URL = "https://rubygems.org/api/v1/versions/rails-ai-context/latest.json"
19
+ INTERVAL = 24 * 60 * 60
20
+
21
+ FETCH = <<~RUBY
22
+ require "net/http"
23
+ path, url = ARGV
24
+ uri = URI(url)
25
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 5, read_timeout: 5) do |http|
26
+ http.get(uri.request_uri)
27
+ end
28
+ version = response.body[/"version"\\s*:\\s*"([^"]+)"/, 1] if response.is_a?(Net::HTTPSuccess)
29
+ exit unless version
30
+ tmp = "\#{path}.\#{Process.pid}"
31
+ begin
32
+ File.write(tmp, version)
33
+ File.rename(tmp, path)
34
+ ensure
35
+ File.delete(tmp) if File.exist?(tmp)
36
+ end
37
+ RUBY
38
+
39
+ module_function
40
+
41
+ # Called before an app's boot, which swaps Gem.path for the bundle's.
42
+ def enable!
43
+ @enabled = true
44
+ @gem_dirs = Gem.path
45
+ newest_at_start
46
+ end
47
+
48
+ # The notice for an agent to pass on. Giving it marks it told, so the CLI does not print it again.
49
+ def agent_note
50
+ note = notice or return
51
+ @told = true
52
+ "Tell the user: #{note}"
53
+ end
54
+
55
+ def told?
56
+ @told == true
57
+ end
58
+
59
+ def disabled?
60
+ !@enabled || !ENV[OPT_OUT_ENV].to_s.empty? || ci? || Gem::Version.new(VERSION).prerelease?
61
+ end
62
+
63
+ # Starts the fetch when the last one is a day old (or in the future, after a
64
+ # clock change). The file's time is set first, so a failed fetch waits a day
65
+ # too. Its own process group keeps a Ctrl-C at the terminal from killing it.
66
+ # @param env [Hash] the environment the binary started in, before an app's boot changed it
67
+ def refresh(env:)
68
+ return if disabled?
69
+
70
+ path = state_file
71
+ age = Time.now - File.mtime(path) if File.exist?(path)
72
+ return if age&.between?(0, INTERVAL)
73
+
74
+ make_dirs(File.dirname(path))
75
+ File.open(path, "a") { nil }
76
+ File.utime(nil, nil, path)
77
+ group = Gem.win_platform? ? { new_pgroup: true } : { pgroup: true }
78
+ pid = Process.spawn(env, RbConfig.ruby, "-e", FETCH, path, URL,
79
+ unsetenv_others: true, in: File::NULL, out: File::NULL, err: File::NULL, **group)
80
+ Process.detach(pid)
81
+ rescue StandardError
82
+ nil
83
+ end
84
+
85
+ # One sentence for the user, or nil when this copy is current.
86
+ def notice
87
+ return if disabled?
88
+
89
+ latest = latest_release or return
90
+ running = Gem::Version.new(VERSION)
91
+ return unless latest > running
92
+ # A newer copy installed when this process started means an older one was run on purpose (`_5.30.0_`).
93
+ return if newest_at_start && newest_at_start > running
94
+
95
+ installed = newest_installed
96
+ if installed && installed >= latest
97
+ "rails-ai-context #{installed} is installed, but this process still runs #{VERSION}. " \
98
+ "Restart the MCP server (or the AI client) to load it."
99
+ else
100
+ "rails-ai-context #{latest} is out; this is #{VERSION} on Ruby #{RUBY_VERSION} (#{RbConfig.ruby}). " \
101
+ "Under that Ruby run `gem update rails-ai-context`, then `rails-ai-context init --defaults` in each app. " \
102
+ "#{OPT_OUT_ENV}=1 turns this check off."
103
+ end
104
+ end
105
+
106
+ def latest_release
107
+ text = File.read(state_file, 64).to_s.strip
108
+ return if text.empty? || !Gem::Version.correct?(text)
109
+
110
+ version = Gem::Version.new(text)
111
+ version unless version.prerelease?
112
+ rescue StandardError
113
+ nil
114
+ end
115
+
116
+ def state_file
117
+ home = Gem.respond_to?(:state_home) ? Gem.state_home : (ENV["XDG_STATE_HOME"] || File.join(Gem.user_home, ".local", "state"))
118
+ File.join(home, "rails-ai-context", "latest")
119
+ end
120
+
121
+ # The newest copy installed for this Ruby, in any directory of the gem path.
122
+ def newest_installed
123
+ Array(@gem_dirs).flat_map { |dir| Dir[File.join(dir, "specifications", "rails-ai-context-*.gemspec")] }.filter_map do |spec|
124
+ number = File.basename(spec, ".gemspec").delete_prefix("rails-ai-context-")
125
+ Gem::Version.new(number) if Gem::Version.correct?(number)
126
+ end.max
127
+ end
128
+
129
+ def newest_at_start
130
+ return @newest_at_start if defined?(@newest_at_start)
131
+
132
+ @newest_at_start = newest_installed
133
+ end
134
+
135
+ def ci?
136
+ defined?(Gem::CIDetector) ? Gem::CIDetector.ci? : !ENV["CI"].to_s.empty?
137
+ end
138
+
139
+ # Dir.mkdir one level at a time: fileutils is a gem an app may pin.
140
+ def make_dirs(dir)
141
+ return if Dir.exist?(dir)
142
+
143
+ make_dirs(File.dirname(dir))
144
+ Dir.mkdir(dir)
145
+ rescue Errno::EEXIST
146
+ nil
147
+ end
148
+ end
149
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsAiContext
4
- VERSION = "5.36.3"
4
+ VERSION = "5.36.4"
5
5
  end
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: 5.36.3
4
+ version: 5.36.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - crisnahine
@@ -560,6 +560,7 @@ files:
560
560
  - lib/rails_ai_context/tools/session_context.rb
561
561
  - lib/rails_ai_context/tools/validate.rb
562
562
  - lib/rails_ai_context/tools/validate_semantics.rb
563
+ - lib/rails_ai_context/update_check.rb
563
564
  - lib/rails_ai_context/version.rb
564
565
  - lib/rails_ai_context/vfs.rb
565
566
  - lib/rails_ai_context/view_file.rb