rails-ai-context 5.21.1 → 5.21.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35ff190a8ba7d3cad8db797b62b37d14595317f0fbb496de79d68a05e31791f2
4
- data.tar.gz: a22572911254cd6ede2927a8833ac8c499ae5a3aee615825daaaa50451a59bf6
3
+ metadata.gz: 986161d7fdb3003eca77d6f88b2820b5b76a35d72b5282d8f910b0e316b29298
4
+ data.tar.gz: 2fc004a71ff022abd1c1d216466936b27249c5da6dede308dece659b3cc311a8
5
5
  SHA512:
6
- metadata.gz: c5afe25035b7a14ba9ed34961a9701c47e5b3f26909c7916514184123cdb1154482769ea018e751b84f5eed71ab4a1bca175a4d22b3e51bf1fccad9fa118e8ae
7
- data.tar.gz: e3973d85c3b0be6dad49ee33f3d918bf9b228e481f600c0435db06ae65a42596d6cc61b968b67b95b01b78bfc35e3c4d43843c8115efca7c00b9278ef8e26d5a
6
+ metadata.gz: 4c335f590a135a3d8591bd52daef9673a59b4b9af150aebbe80cf91a39905098c7e215f5e77dea0dd07abf1aa9d94c96ac3586a2d487f0955ab3eb6220f51bc5
7
+ data.tar.gz: 230525933f4ccd784307c70ec97785b3b7df2cafedcb4720b9d7ae5d6b95239693ecd9e11d0651215ce177a4cf8e1144ef85dee82a061f62b21f0252e1fe5523
data/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ 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.21.2] - 2026-08-12
9
+
10
+ ### Fixed
11
+
12
+ - **The static tier no longer drops a model's concerns.** Nothing in the
13
+ listener stack collected `include`/`prepend`, so `rails_get_model_details`
14
+ answered `--no-boot` with no Concerns section at all - not even an
15
+ `[UNAVAILABLE]` marker - for a model that includes one. A new
16
+ `MixinsListener` reads them from the source, filtered to the mixins
17
+ reflection would report so both tiers answer the same question. (#137)
18
+ - **Cookie settings that Rails computes per request are named, not
19
+ inspected.** Rails 8 defaults `same_site` to a lambda, which reached
20
+ `.ai-context.json` as `#<Proc:0x...>` - an address that answers nothing and
21
+ moves on every boot, so the file was rewritten on every run even when the app
22
+ had not changed. Callables now render `[UNAVAILABLE: computed at request
23
+ time]`, nested ones included. (#138)
24
+ - **Initializer sources are gem-relative instead of absolute machine paths.**
25
+ Every one of them was written into `.ai-context.json` as
26
+ `/Users/<name>/.rvm/gems/ruby-3.4.9/gems/railties-8.0.5.1/...`, which is
27
+ wrong on every other machine, wrong again after a Ruby upgrade, and carries
28
+ the generating developer's home directory into the app's repository. (#139)
29
+
8
30
  ## [5.21.1] - 2026-08-12
9
31
 
10
32
  ### Fixed
@@ -185,6 +185,7 @@ It runs a single-pass Dispatcher that walks the AST once and feeds events to all
185
185
  | CallbacksListener | All AR callback types, `after_commit` with `on:` resolution |
186
186
  | MacrosListener | `encrypts`, `normalizes`, `delegate`, `has_secure_password`, `serialize`, `store`, `has_one_attached`, `has_many_attached`, `has_rich_text`, `generates_token_for`, `attribute` |
187
187
  | MethodsListener | `def`/`def self.`, visibility tracking, parameter extraction, `class << self` |
188
+ | MixinsListener | `include`, `prepend`, `extend`, flagging the ones that reach the ancestor chain |
188
189
 
189
190
  ### The targeted-walk listeners
190
191
 
@@ -110,12 +110,29 @@ module RailsAiContext
110
110
  return nil unless loc
111
111
 
112
112
  path, line = loc
113
- path = path.sub("#{root}/", "") if path.start_with?(root)
114
- "#{path}:#{line}"
113
+ "#{relativize_source(path)}:#{line}"
115
114
  rescue => e
116
115
  $stderr.puts "[rails-ai-context] block_source_location failed: #{e.message}" if ENV["DEBUG"]
117
116
  nil
118
117
  end
118
+
119
+ # These sources are written into .ai-context.json, which the app commits.
120
+ # An absolute path is wrong on every other machine and carries the
121
+ # generating developer's home directory, so app paths go app-relative and
122
+ # gem paths keep the gem and version instead of the install prefix.
123
+ def relativize_source(path)
124
+ return path.sub("#{root}/", "") if path.start_with?(root)
125
+
126
+ gem_roots.each do |gem_root|
127
+ return path.delete_prefix(gem_root) if path.start_with?(gem_root)
128
+ end
129
+ path
130
+ end
131
+
132
+ def gem_roots
133
+ @gem_roots ||= (Gem.path + [ Gem.default_dir ]).compact.uniq
134
+ .map { |dir| File.join(dir, "gems") + File::SEPARATOR }
135
+ end
119
136
  end
120
137
  end
121
138
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module RailsAiContext
6
+ module Introspectors
7
+ module Listeners
8
+ # Detects `include`, `prepend` and `extend` with a constant argument.
9
+ #
10
+ # `ancestor` marks the ones that reach the ancestor chain, which is what
11
+ # reflection reports - so a caller can answer the same question the
12
+ # booted tier answers.
13
+ class MixinsListener < BaseListener
14
+ MIXIN_MACROS = %i[include prepend extend].to_set.freeze
15
+ ANCESTOR_MACROS = %i[include prepend].to_set.freeze
16
+
17
+ def initialize
18
+ super
19
+ @singleton_depth = 0
20
+ end
21
+
22
+ # `include` inside `class << self` lands on the singleton class, so it
23
+ # never reaches `ancestors` - extend semantics wearing an include's name.
24
+ def on_singleton_class_node_enter(_node)
25
+ @singleton_depth += 1
26
+ end
27
+
28
+ def on_singleton_class_node_leave(_node)
29
+ @singleton_depth -= 1
30
+ end
31
+
32
+ def on_call_node_enter(node)
33
+ return unless node.receiver.nil?
34
+ return unless MIXIN_MACROS.include?(node.name)
35
+
36
+ (node.arguments&.arguments || []).each do |arg|
37
+ name = constant_name(arg)
38
+ next unless name
39
+
40
+ @results << {
41
+ macro: node.name,
42
+ name: name,
43
+ ancestor: @singleton_depth.zero? && ANCESTOR_MACROS.include?(node.name),
44
+ location: node.location.start_line,
45
+ confidence: confidence_for(node)
46
+ }
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def constant_name(node)
53
+ return unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
54
+
55
+ extract_value(node)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -591,11 +591,23 @@ module RailsAiContext
591
591
  # with callbacks found" and then raised a TypeError on a Hash lookup
592
592
  # against an Array.
593
593
  callbacks: group_callbacks_by_type(data[:callbacks]),
594
+ concerns: static_concerns(data[:mixins]),
594
595
  macros: data[:macros],
595
596
  methods: data[:methods]
596
597
  }
597
598
  end
598
599
 
600
+ # Only the mixins reflection would report, so both tiers answer the same
601
+ # question. This sees the model file alone, where the booted tier also
602
+ # walks what its superclass and its concerns pulled in.
603
+ def static_concerns(mixins)
604
+ (mixins || [])
605
+ .select { |mixin| mixin[:ancestor] }
606
+ .map { |mixin| mixin[:name] }
607
+ .reject { |name| framework_concern?(name) }
608
+ .uniq
609
+ end
610
+
599
611
  # Mongoid documents are invisible to ActiveRecord reflection, so both
600
612
  # tiers parse them from source. Fields and embedded relations come from
601
613
  # the Mongoid listener; shared-name macros (belongs_to, has_many,
@@ -246,11 +246,17 @@ module RailsAiContext
246
246
  {}
247
247
  end
248
248
 
249
+ # Rails 8 defaults same_site to a lambda. Left alone it reaches
250
+ # .ai-context.json as an address that answers nothing and moves every
251
+ # boot, so the file is rewritten on every run. Nested one level down it
252
+ # is worse: the encoder renders it `{}`, which reads as a setting the app
253
+ # left empty.
249
254
  def serializable(value)
250
255
  case value
251
256
  when Symbol then value.to_s
252
- when Hash then value.transform_keys(&:to_s)
253
- when Array then value.map(&:to_s)
257
+ when Hash then value.each_with_object({}) { |(key, nested), result| result[key.to_s] = serializable(nested) }
258
+ when Array then value.map { |nested| serializable(nested) }
259
+ when Proc then Confidence.unavailable("computed at request time")
254
260
  else value
255
261
  end
256
262
  end
@@ -20,7 +20,8 @@ module RailsAiContext
20
20
  enums: Listeners::EnumsListener,
21
21
  callbacks: Listeners::CallbacksListener,
22
22
  macros: Listeners::MacrosListener,
23
- methods: Listeners::MethodsListener
23
+ methods: Listeners::MethodsListener,
24
+ mixins: Listeners::MixinsListener
24
25
  }.freeze
25
26
 
26
27
  # Introspect a file on disk (cached parse) with default listeners.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsAiContext
4
- VERSION = "5.21.1"
4
+ VERSION = "5.21.2"
5
5
  end
data/server.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "url": "https://github.com/crisnahine/rails-ai-context",
8
8
  "source": "github"
9
9
  },
10
- "version": "5.21.0",
10
+ "version": "5.21.1",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "mcpb",
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.21.1
4
+ version: 5.21.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - crisnahine
@@ -345,6 +345,7 @@ files:
345
345
  - lib/rails_ai_context/introspectors/listeners/methods_listener.rb
346
346
  - lib/rails_ai_context/introspectors/listeners/middleware_config_listener.rb
347
347
  - lib/rails_ai_context/introspectors/listeners/migration_dsl_listener.rb
348
+ - lib/rails_ai_context/introspectors/listeners/mixins_listener.rb
348
349
  - lib/rails_ai_context/introspectors/listeners/model_reference_listener.rb
349
350
  - lib/rails_ai_context/introspectors/listeners/mongoid_fields_listener.rb
350
351
  - lib/rails_ai_context/introspectors/listeners/mount_listener.rb