The Namespace Cartographer
The Namespace Cartographer: maps a gem's constant tree and audits every file against the constant Zeitwerk expects it to define. One orchestrator task per file; Prism reads the actual definitions.
Plans & Graphs
Round 2
Xavier Noria
exit 0
bundle exec ruby examples/namespace_cartographer.rb
a real captured run
NAMESPACE MAP of /opt/hostedtoolcache/Ruby/3.3.11/x64/lib/ruby/gems/3.3.0/bundler/gems/agentic-0fe3f91c1ef9/lib (68 files surveyed, completed in 482ms) agentic 1 file(s) agentic/adaptation_engine 1 file(s) agentic/agent 1 file(s) agentic/agent_assembly_engine 1 file(s) agentic/agent_capability_registry 1 file(s) agentic/agent_config 1 file(s) agentic/agent_specification 1 file(s) agentic/callable_agent 1 file(s) agentic/capabilities 3 file(s) agentic/capability_provider 1 file(s) agentic/capability_specification 1 file(s) agentic/capability_validator 1 file(s) agentic/cli 5 file(s) agentic/default_agent_provider 1 file(s) agentic/errors 1 file(s) agentic/execution_journal 1 file(s) agentic/execution_plan 1 file(s) agentic/execution_result 1 file(s) agentic/expected_answer_format 1 file(s) agentic/extension 4 file(s) agentic/factory_methods 1 file(s) agentic/generation_stats 1 file(s) agentic/learning 5 file(s) agentic/llm_assisted_composition_strategy 1 file(s) agentic/llm_client 1 file(s) agentic/llm_config 1 file(s) agentic/llm_response 1 file(s) agentic/logger 1 file(s) agentic/named_outputs 1 file(s) agentic/observable 1 file(s) agentic/persistent_agent_store 1 file(s) agentic/plan_execution_result 1 file(s) agentic/plan_orchestrator 1 file(s) agentic/plan_orchestrator_config 1 file(s) agentic/rate_limit 1 file(s) agentic/relation_rules 1 file(s) agentic/retry_config 1 file(s) agentic/retry_handler 1 file(s) agentic/structured_outputs 1 file(s) agentic/suggestions 1 file(s) agentic/task 1 file(s) agentic/task_definition 1 file(s) agentic/task_execution_result 1 file(s) agentic/task_failure 1 file(s) agentic/task_output_schemas 1 file(s) agentic/task_planner 1 file(s) agentic/task_result 1 file(s) agentic/ui 1 file(s) agentic/verification 6 file(s) agentic/version 1 file(s) Every file defines the constant its path promises. The map IS the territory.
source
# frozen_string_literal: true # The Namespace Cartographer: maps a gem's constant tree and audits # every file against the constant Zeitwerk expects it to define. # One orchestrator task per file; Prism reads the actual definitions. # # bundle exec ruby examples/namespace_cartographer.rb [lib_dir] # # Defaults to surveying this gem. A conforming codebase produces a map # with no annotations; every deviation is listed with what was expected # and what was found. require class="s">"bundler/setup" require class="s">"agentic" require class="s">"prism" LIB = ARGV.first ? File.expand_path(ARGV.first) : File.join(Gem:class="y">:Specification.find_by_name(class="s">"agentic").gem_dir, class="s">"lib") # the installed gem, wherever bundler put it INFLECTIONS = {class="s">"cli" => class="s">"CLI", class="s">"ui" => class="s">"UI"}.freeze def camelize(segment) INFLECTIONS.fetch(segment) { segment.split(class="s">"_").map(&class="y">:capitalize).join } end # The constant Zeitwerk expects lib/foo/bar_baz.rb to define. # Zeitwerk::GemInflector special-cases the gem's version.rb: it expects # Foo::VERSION, not Foo::Version - a lesson this cartographer learned # by first drawing the deviation on its own map. def expected_constant(relative_path) segments = relative_path.delete_suffix(class="s">".rb").split(class="s">"/") return class="s">"#{camelize(segments.first)}:class="y">:VERSION" if segments.length == 2 && segments.last == class="s">"version" segments.map { |seg| camelize(seg) }.join(class="s">"::") end # Collects every module/class defined in a parse tree, as full paths def collect_definitions(node, namespace, found) return unless node case node when Prism:class="y">:ModuleNode, Prism:class="y">:ClassNode name = node.constant_path.slice full = [namespace, name].reject(&class="y">:empty?).join(class="s">"::") found << full node.child_nodes.each { |child| collect_definitions(child, full, found) } when Prism:class="y">:ConstantWriteNode found << [namespace, node.name.to_s].reject(&class="y">:empty?).join(class="s">"::") else node.child_nodes.each { |child| collect_definitions(child, namespace, found) } end end spec = Agentic:class="y">:CapabilitySpecification.new( name: class="s">"survey_file", description: class="s">"Chart the constants a Ruby file defines", version: class="s">"1.0.0", inputs: {path: {type: class="s">"string", required: true}}, outputs: {defined: {type: class="s">"array", required: true}} ) Agentic.register_capability(spec, Agentic:class="y">:CapabilityProvider.new( capability: spec, implementation: ->(inputs) { found = [] collect_definitions(Prism.parse_file(inputs[class="y">:path]).value, class="s">"", found) {defined: found} } )) surveyor = Agentic:class="y">:Agent.build { |a| a.name = class="s">"Cartographer" } surveyor.add_capability(class="s">"survey_file") files = Dir[File.join(LIB, class="s">"**", class="s">"*.rb")].sort if files.empty? puts class="s">" no ruby files under #{LIB} - an empty survey proves nothing" exit 1 end orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 8) tasks = files.to_h do |path| task = Agentic:class="y">:Task.new( description: File.basename(path), agent_spec: {class="s">"name" => class="s">"Cartographer", class="s">"instructions" => class="s">"Survey the file"}, payload: path ) orchestrator.add_task(task, agent: ->(t) { surveyor.execute_capability(class="s">"survey_file", {path: t.payload})[class="y">:defined] }) [path, task] end result = orchestrator.execute_plan charts = tasks.transform_values { |task| result.results[task.id].output } # Compare the map against the territory deviations = [] tree = Hash.new(0) files.each do |path| relative = path.delete_prefix(class="s">"#{LIB}/") expected = expected_constant(relative) defined = charts.fetch(path, []) tree[relative.split(class="s">"/").first(2).join(class="s">"/").delete_suffix(class="s">".rb")] += 1 unless defined.include?(expected) deviations << {file: relative, expected: expected, found: defined.first(3)} end end puts class="s">"NAMESPACE MAP of #{LIB}" puts class="s">"(#{files.size} files surveyed, #{result.status} in #{(result.execution_time * 1000).round}ms)" puts tree.sort.each { |region, count| puts format(class="s">" %-46s %3d file(s)", region, count) } puts if deviations.empty? puts class="s">"Every file defines the constant its path promises. The map IS the territory." else puts class="s">"DEVIATIONS (#{deviations.size}):" deviations.each do |d| puts class="s">" #{d[class="y">:file]}" puts class="s">" expected #{d[class="y">:expected]}, defines #{d[class="y">:found].join(", class="s">")}" end end