Setup: 0.9.12, uv, macOS, same Rails repo as #1634 / #1640 / #1666.
.rake files are plain Ruby (Rake's task DSL is ordinary Ruby method calls), but the extension is absent from CODE_EXTENSIONS (detect.py L30), so classify_file returns None and every rake task is skipped. The 0.9.9 not-classified warning does surface them (nice: this is not a silent #1666-style drop), but the graph still ends up blind to them.
Expected: .rake routes to the Ruby extractor and participates in Ruby cross-file resolution, exactly like .rb.
Repro (two files):
# ops.rake
class RakeHelper
def self.run
Widget.tally
end
end
namespace :ops do
task fix: :environment do
RakeHelper.run
end
end
# widget.rb
class Widget
def self.tally
42
end
end
graphify extract . --code-only → found 1 code, 1 file(s) not classified (no supported extension or shebang), skipped: ops.rake. Only widget.rb's 3 nodes exist; the Widget.tally call site is invisible.
The extractor already handles the content: copy ops.rake to ops_renamed.rb and re-run, and you get the RakeHelper class node, its .run() method, and the cross-file calls edge to widget_widget_tally. So this is purely extension routing, not parsing.
Fix map: the extension is gated in more places than the obvious two. Surveying 0.9.12 for functional .rb gates:
detect.py L30: CODE_EXTENSIONS (classification)
extract.py L3764: ".rb": extract_ruby (extractor dispatch)
extract.py L1781: ".rb": "ruby" (language-name map)
extract.py L2708: LanguageResolver("ruby_member_calls", frozenset({".rb"}), …), the corpus-wide member-call resolver, is extension-gated
ruby_resolution.py L47 + L86: the Ruby cross-file resolution passes filter candidate nodes by sf.endswith(".rb")
analyze.py L31: language stats map
build.py L52: ".rb": "rb" tag map
Worth calling out: patching only 1-3 makes .rake files appear fixed (nodes show up) while 4-5 silently exclude them from cross-file call/constant resolution: nodes without their edges. A shared RUBY_EXTENSIONS = {".rb", ".rake"} constant (or an is_ruby_source() helper) would close all seven at once and make future Ruby-family additions a one-line change.
Why it matters on a real Rails repo: we have 42 tracked .rake files (+ Rakefile). In a 12-entity blast-radius grading on our graph (affected --depth 1 vs exhaustive grep ground truth), ~17% of all true dependents (40/242) were rake tasks, and rake tasks are exactly the files you most want in a blast radius: data backfills, recovery tooling, ops entry points that break silently when a model changes.
Possible follow-up in the same vein (separate scope): extensionless Ruby basenames (Rakefile, Gemfile, Guardfile) currently fall through to the shebang probe and are skipped too; basename-based routing already exists for package manifests, so the same mechanism would cover them.
Happy to re-run our census + recall grading the day this ships.
Setup: 0.9.12, uv, macOS, same Rails repo as #1634 / #1640 / #1666.
.rakefiles are plain Ruby (Rake's task DSL is ordinary Ruby method calls), but the extension is absent fromCODE_EXTENSIONS(detect.py L30), soclassify_filereturns None and every rake task is skipped. The 0.9.9 not-classified warning does surface them (nice: this is not a silent #1666-style drop), but the graph still ends up blind to them.Expected:
.rakeroutes to the Ruby extractor and participates in Ruby cross-file resolution, exactly like.rb.Repro (two files):
graphify extract . --code-only→found 1 code,1 file(s) not classified (no supported extension or shebang), skipped: ops.rake. Onlywidget.rb's 3 nodes exist; theWidget.tallycall site is invisible.The extractor already handles the content: copy
ops.raketoops_renamed.rband re-run, and you get theRakeHelperclass node, its.run()method, and the cross-filecallsedge towidget_widget_tally. So this is purely extension routing, not parsing.Fix map: the extension is gated in more places than the obvious two. Surveying 0.9.12 for functional
.rbgates:detect.pyL30:CODE_EXTENSIONS(classification)extract.pyL3764:".rb": extract_ruby(extractor dispatch)extract.pyL1781:".rb": "ruby"(language-name map)extract.pyL2708:LanguageResolver("ruby_member_calls", frozenset({".rb"}), …), the corpus-wide member-call resolver, is extension-gatedruby_resolution.pyL47 + L86: the Ruby cross-file resolution passes filter candidate nodes bysf.endswith(".rb")analyze.pyL31: language stats mapbuild.pyL52:".rb": "rb"tag mapWorth calling out: patching only 1-3 makes
.rakefiles appear fixed (nodes show up) while 4-5 silently exclude them from cross-file call/constant resolution: nodes without their edges. A sharedRUBY_EXTENSIONS = {".rb", ".rake"}constant (or anis_ruby_source()helper) would close all seven at once and make future Ruby-family additions a one-line change.Why it matters on a real Rails repo: we have 42 tracked
.rakefiles (+ Rakefile). In a 12-entity blast-radius grading on our graph (affected --depth 1vs exhaustive grep ground truth), ~17% of all true dependents (40/242) were rake tasks, and rake tasks are exactly the files you most want in a blast radius: data backfills, recovery tooling, ops entry points that break silently when a model changes.Possible follow-up in the same vein (separate scope): extensionless Ruby basenames (
Rakefile,Gemfile,Guardfile) currently fall through to the shebang probe and are skipped too; basename-based routing already exists for package manifests, so the same mechanism would cover them.Happy to re-run our census + recall grading the day this ships.