Ruby calls without parentheses are calls.
The Rails user behind 3.3.1 sent the list that matters in a real codebase: delegate, concerns pulled in with a plain include, has_many through: with source:, and public_send. Measured on 3.3.1, each one dead-ended somewhere, and so did something larger: a hand-written method called without parentheses, which in Ruby is most calls, was never linked at all. 3.3.2 follows them, through the class hierarchy Ruby itself would use, and checked on two open-source Rails apps, maybe-finance and Mastodon, before shipping. It also stops a Dart dot shorthand in a list from costing the whole file, and gives the self-hosted SDK a free Dev license you can get yourself, with the gateway holding every plan to its limits.
Every Ruby call, with or without parentheses. post.author.full_name, a bare display_name inside User, User.find_active and self.badge are now calls in vexp impact and flow, whether the method is written by hand or generated by Rails. The receiver decides the class: a variable named post is a Post, current_user a User, a constant is that class, and a hop of a chain is what the association before it returns. A method is never linked by its name alone, so x.name or x.save on a receiver vexp cannot read stays unlinked instead of pointing at every name or save in the codebase; active? is the predicate, never the scope active.
Concerns, parent classes and delegate. A class reaches what its superclass and the modules it includes declare, so post.taggings lands on the Taggable concern and admin.display_name on User. Methods written in class << self, included do and class_methods do are now symbols too. delegate :full_name, to: :author, prefix: true defines author_full_name, and impact follows it through to User#full_name. Constants resolve the way Ruby resolves them, so two concerns that are both called Provided (Security::Provided, ExchangeRate::Provided) are never mixed up, and the model copies a migration declares stay out of the app’s classes.
has_many through: follows source:. has_many :subscribers, through: :subscriptions, source: :user now returns Users, the way Rails reads it, so post.subscribers.active reaches User’s active scope instead of stopping at a Subscriber class that does not exist. Without source:, vexp looks for the source association on the through model, as Rails does, and class_name: and source_type: are followed along the chain.
public_send is a call, or a flag. public_send(:publish), send("publish") and try(:publish) are calls to publish. When the name is computed (post.public_send(field)), vexp cannot know the method, so it no longer drops the call silently: vexp impact on a method of that class lists it as a dynamic call that may also reach it, and counts the dynamic calls whose receiver it cannot read.
Dart: a dot shorthand in a list no longer costs the file. A dot shorthand used as a list element, a map value or a record field (.active in [.active, .paused]) made the Dart grammar give up on the enclosing code, and in a class the whole file was lost. vexp now recovers those files: on 663 Dart files of open-source apps, the files lost whole went from 8 to 0. A file an older vexp could only partly read is read again at the next start, with no rebuild.
SDK: a one-repository trial, a free Dev tier a form away, both metered. The free Dev license for the self-hosted SDK gateway is now self-serve at vexp.dev/sdk/dev: an email, a link, a license, and the same again for a new one after 30 days. The gateway counts the Dev tier’s 30 lookups a day itself (run_pipeline, get_context_capsule, get_skeleton), once per request however many shards it reaches, and keeps the count across restarts. Every metered answer says how many are left; past the limit those three answer 429 with Retry-After until 00:00 UTC, while status, indexing and webhooks keep working. Without an SDK license, or with an expired one, the gateway is a trial on one repository with 20 lookups a day, says so at startup and in /health, and points to the Dev license when the day’s lookups run out. A consumer license, which covers a person’s own work in the editor and agent CLIs, counts as no SDK license. Standalone shard processes keep to the same repository band as in-process ones.
Upgrade note: a rebuild only where there is Ruby. An index that contains Ruby is rebuilt once at the first start after the upgrade, about as long as a first index; every other index is left as it is. vexp index --status shows “Parser: 3.3.2 (current)” when it is done.