Class: Autotype::CLI

Inherits:
Object
  • Object
show all
Defined in:
lib/autotype/engine.rb

Class Method Summary collapse

Class Method Details

.build_metadata(collectors, profile: default_profile) ⇒ Object



4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
# File 'lib/autotype/engine.rb', line 4456

def self.(collectors, profile: default_profile)
  profile.finalize!(collectors) if profile.respond_to?(:finalize!)
   = InferenceMetadata.new(
    member_type_hints: profile.member_type_hints,
    option_type_hints: profile.option_type_hints,
    side_effect_methods: profile.side_effect_methods,
    structured_type_prefixes: profile.structured_type_prefixes,
    port_wiring: profile.port_wiring,
    output_emit: profile.output_emit,
    config_hash: profile.config_hash,
    framework_self_fallbacks: profile.framework_self_fallbacks,
    type_locators: [profile.method(:locate_type_file)]
  )
  collectors.each do |collector|
    .merge_ports!(collector.declared_ports)
    .merge_member_types!(collector.declared_member_types)
    .merge_config_options!(collector.declared_config_options)
    collector.structured_owners.each { |owner| .structured_owners << owner }
    collector.referenced_types.each { |type| .referenced_types << type }
  end
  
end

.default_profileObject



4433
4434
4435
# File 'lib/autotype/engine.rb', line 4433

def self.default_profile
  Autotype.profile
end

.expand_referenced_type_files(files, profile: default_profile) ⇒ Object



4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
# File 'lib/autotype/engine.rb', line 4437

def self.expand_referenced_type_files(files, profile: default_profile)
  expanded = files.dup
  referenced = Set.new
  files.each do |path|
    next unless File.file?(path)

    result = Prism.parse_file(path)
    next unless result.success?

    collector = Collector.new(path, profile: profile)
    result.value.accept(collector)
    collector.referenced_types.each do |type_name|
      type_path = profile.locate_type_file(type_name)
      referenced << type_path if type_path
    end
  end
  (expanded + referenced.to_a).uniq
end

.load_capability_names(path) ⇒ Object



4608
4609
4610
4611
# File 'lib/autotype/engine.rb', line 4608

def self.load_capability_names(path)
  paths = File.directory?(path) ? Dir[File.join(path, "*.json")].sort : [path]
  paths.each_with_object({}) { |file, names| names.merge!(JSON.parse(File.read(file))) }
end

.run(argv) ⇒ Object



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 }
   = (collectors, profile: profile)
  inferencer = FixedPointInferencer.new(
    method_reports.map(&:last),
    constants: constants,
    includes: includes,
    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)
  # Deterministic LLM-free names first; the OpenAI flags below override.
  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

.skipped_filesObject



4429
4430
4431
# File 'lib/autotype/engine.rb', line 4429

def self.skipped_files
  Autotype.configuration.skipped_files
end