agentic examples

The Refactoring Dojo

The Refactoring Dojo: a student submits a method, three critic agents review it from three distinct perspectives, and the sensei prescribes the ONE smallest next step. Today's student: this gem itself, submitting its second-longest method (schedule_task, 90 lines).

Testing & Verification Round 2 Sandi Metz exit 0

source on github

bundle exec ruby examples/refactoring_dojo.rb

a real captured run

REFACTORING DOJO
submission: #schedule_task (52 lines) from plan_orchestrator.rb

rule keeper says:
  - 52 lines; the rule is five. Every extra line is a place for a bug to live.

squint tester says:
  - squinting shows 4 levels of shape change - each ridge is a concept asking for its own method.

name watcher says:
  - no complaints. rare.

sensei's prescription:
  2 findings, ONE next step - the smallest one:
  start where the squint test hurts: squinting shows 4 levels of shape change - each ridge is a concept asking for its own method.
  make that change, run the tests, come back. refactoring is many small
  safe steps, not one brave rewrite.

source

# frozen_string_literal: true

# The Refactoring Dojo: a student submits a method, three critic agents
# review it from three distinct perspectives, and the sensei prescribes
# the ONE smallest next step. Today's student: this gem itself,
# submitting its second-longest method (schedule_task, 90 lines).
#
#   bundle exec ruby examples/refactoring_dojo.rb [file] [method]
#
# Runs offline: the critics measure; they don't guess.

require class="s">"bundler/setup"
require class="s">"agentic"

# These examples read the agentic SOURCE - resolve the installed gem's own directory
AGENTIC_SRC = Gem:class="y">:Specification.find_by_name(class="s">"agentic").gem_dir
require class="s">"prism"

file = ARGV[0] || File.join(AGENTIC_SRC, class="s">"lib/agentic/plan_orchestrator.rb")
method_name = (ARGV[1] || class="s">"schedule_task").to_sym

# Find the student's submission
def find_def(node, name)
  return node if node.is_a?(Prism:class="y">:DefNode) && node.name == name

  node&.child_nodes&.each do |child|
    found = find_def(child, name)
    return found if found
  end
  nil
end

submission = find_def(Prism.parse_file(file).value, method_name) ||
  abort(class="s">"no method #{method_name} in #{file}")
source = submission.slice
lines = source.lines

def critic(name, &impl)
  spec = Agentic:class="y">:CapabilitySpecification.new(
    name: name, description: class="s">"The #{name} critic", version: class="s">"1.0.0",
    inputs: {source: {type: class="s">"string", required: true}},
    outputs: {findings: {type: class="s">"array", required: true}}
  )
  Agentic.register_capability(
    spec, Agentic:class="y">:CapabilityProvider.new(capability: spec, implementation: impl)
  )

  Agentic:class="y">:Agent.build { |a| a.name = name }.tap { |a| a.add_capability(name) }
end

critics = []

critics << critic(class="s">"rule_keeper") do |input|
  body = input[class="y">:source].lines
  findings = []
  if body.size > 5
    findings << class="s">"#{body.size} lines; the rule is five. Every extra line is a place for a bug to live."
  end
  params = body.first[/\((.*)\)/, 1].to_s.split(class="s">",").size
  if params > 4
    findings << class="s">"#{params} parameters; four is the ceiling before a parameter object is cheaper."
  end
  {findings: findings}
end

critics << critic(class="s">"squint_tester") do |input|
  body = input[class="y">:source].lines
  indents = body.map { |l| l[/\A */].size }.reject(&class="y">:zero?)
  depth = (indents.max - indents.min) / 2
  findings = []
  if depth >= 3
    findings << class="s">"squinting shows #{depth} levels of shape change - each ridge is a concept asking for its own method."
  end
  branches = body.count { |l| l.strip.start_with?(class="s">"if ", class="s">"elsif ", class="s">"unless ", class="s">"when ", class="s">"rescue") }
  if branches >= 4
    findings << class="s">"#{branches} branch points in one method - this method makes decisions AND does work; split the two."
  end
  {findings: findings}
end

critics << critic(class="s">"name_watcher") do |input|
  body = input[class="y">:source]
  findings = []
  vague = body.scan(/\b(data|info|result|temp|obj|thing)\b/).flatten.tally
  vague.each do |word, count|
    findings << class="s">"'#{word}' appears #{count}x - a name that could mean anything means nothing. What IS it?"
  end
  if body.match?(/def \w+_and_\w+/)
    findings << class="s">"the name contains 'and' - a confession that this is two methods in one costume."
  end
  {findings: findings}
end

# The circle convenes: each critic rides its own task and reviews in parallel
orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 3)
seats = critics.to_h do |c|
  task = Agentic:class="y">:Task.new(
    description: c.name,
    agent_spec: {class="s">"name" => c.name, class="s">"instructions" => class="s">"Review the submission"},
    payload: c
  )
  orchestrator.add_task(task, agent: ->(t) {
    t.payload.execute_capability(t.payload.name, {source: source})[class="y">:findings]
  })
  [c.name, task]
end
run = orchestrator.execute_plan
scrolls = seats.transform_values { |task| run.results[task.id].output }

puts class="s">"REFACTORING DOJO"
puts class="s">"submission: ##{method_name} (#{lines.size} lines) from #{File.basename(file)}"
puts
scrolls.each do |critic_name, findings|
  puts class="s">"#{critic_name.tr("_class="s">", " class="s">")} says:"
  findings.each { |f| puts class="s">"  - #{f}" }
  puts class="s">"  - no complaints. rare." if findings.empty?
  puts
end

total = scrolls.values.sum(&class="y">:size)
puts class="s">"sensei's prescription:"
if total.zero?
  puts class="s">"  ship it, then find a harder kata."
else
  first_move = scrolls[class="s">"squint_tester"]&.first || scrolls.values.flatten.first
  puts class="s">"  #{total} findings, ONE next step - the smallest one:"
  puts class="s">"  start where the squint test hurts: #{first_move}"
  puts class="s">"  make that change, run the tests, come back. refactoring is many small"
  puts class="s">"  safe steps, not one brave rewrite."
end