4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
|
# File 'lib/autotype/engine.rb', line 4479
def self.run(argv)
json = false
html_path = nil
config_path = ENV["AUTOTYPE_CONFIG"]
capability_names_path = nil
naming_cache_path = nil
naming_model = OpenAiCapabilityNamer::DEFAULT_MODEL
naming_batch_size = OpenAiCapabilityNamer::DEFAULT_BATCH_SIZE
dump_capabilities_path = nil
parser = OptionParser.new do |options|
options.banner = "Usage: autotype [OPTIONS] FILE..."
options.on("--config PATH", "Path to autotype.yml (default: discover from cwd)") do |path|
config_path = path
end
options.on("--json", "Emit machine-readable JSON") { json = true }
options.on("--html PATH", "Write a self-contained HTML typedoc") { |path| html_path = path }
options.on("--capability-names PATH", "Load semantic names from a JSON file or directory") do |path|
capability_names_path = path
end
options.on("--name-capabilities PATH", "Call OpenAI Responses API and cache semantic names") do |path|
naming_cache_path = path
end
options.on("--capability-model MODEL", "OpenAI model used for naming (default: gpt-5.6-luna)") do |model|
naming_model = model
end
options.on("--capability-batch-size N", Integer, "Capabilities per model call (default: 100)") do |size|
naming_batch_size = size
end
options.on("--dump-capabilities PATH", "Write universal definitions as JSON Lines") do |path|
dump_capabilities_path = path
end
end
files = parser.parse(argv)
abort parser.to_s if files.empty?
abort "--json and --html are mutually exclusive" if json && html_path
Autotype.configuration.config_path = config_path if config_path
Autotype.configuration.reload_profile!
files.reject! { |path| skipped_files.include?(path.delete_prefix("./")) }
profile = default_profile
profile.prepare_search!(files) if profile.respond_to?(:prepare_search!)
files = expand_referenced_type_files(files, profile: profile)
parse_errors = {}
constants = {}
includes = Hash.new { |hash, key| hash[key] = [] }
collectors = []
method_reports = files.flat_map do |path|
result = Prism.parse_file(path)
unless result.success?
warn "#{path}: parse failed"
result.errors.each { |error| warn " L#{error.location.start_line}: #{error.message}" }
parse_errors[path] = result.errors.map { |error| "L#{error.location.start_line}: #{error.message}" }
next []
end
collector = Collector.new(path, profile: profile)
result.value.accept(collector)
collectors << collector
constants.merge!(collector.constants)
collector.includes.each { |owner, mods| includes[owner].concat(mods) }
collector.methods.map { |method| [path, method] }
end
includes.transform_values! { |mods| mods.uniq }
metadata = build_metadata(collectors, profile: profile)
inferencer = FixedPointInferencer.new(
method_reports.map(&:last),
constants: constants,
includes: includes,
metadata: metadata
)
analyzed_methods = method_reports.map(&:last)
solved_methods = inferencer.run
method_reports = method_reports.zip(solved_methods, analyzed_methods).map do |(path, _method), solved_method, analyzed_method|
[path, solved_method, analyzed_method]
end
unless json
status = inferencer.converged? ? "converged" : "reached its iteration limit"
puts "Type inference #{status} after #{inferencer.iterations} iteration(s)"
end
helper_registry = UniversalHelperRegistry.new(analyzed_methods)
HeuristicCapabilityNamer.new(helper_registry).name!
if naming_cache_path
OpenAiCapabilityNamer.new(
helper_registry,
cache_path: naming_cache_path,
model: naming_model,
batch_size: naming_batch_size
).name!
end
if dump_capabilities_path
lines = helper_registry.definitions.map do |definition|
JSON.generate(id: definition.fetch(:id), capability: definition.fetch(:definition))
end
File.write(dump_capabilities_path, "#{lines.join("\n")}\n")
if html_path.nil? && !json
puts "Wrote #{dump_capabilities_path} (#{lines.length} universal capability types)"
return
end
end
if capability_names_path
helper_registry.apply_names!(load_capability_names(capability_names_path))
end
reports = method_reports.map do |path, method, analyzed_method|
[path, Renderer.new(method, analyzed_method: analyzed_method, helper_registry: helper_registry)]
end
if html_path
document = HtmlDocument.new(reports, analyzed_files: files, parse_errors: parse_errors)
File.write(html_path, document.render)
puts "Wrote #{html_path} (#{reports.length} methods across #{reports.map(&:first).uniq.length} files)"
elsif json
puts JSON.pretty_generate(
reports.group_by(&:first).transform_values { |entries| entries.map { |_, renderer| renderer.as_json } }
)
else
reports.group_by(&:first).each do |path, entries|
puts path
puts "=" * path.length
entries.each do |_, renderer|
puts renderer.text
puts
end
end
end
end
|