agentic examples

Graph to Specs

Graph to Specs: the plan's structure dictates its test plan - roots need fixture cases, joins need one case per missing tributary, leaves need output assertions. This generates the RSpec skeleton from the graph, so "what should we test?" stops being a staring contest with a blank file.

Testing & Verification Round 8 Sandi Metz exit 0

source on github

bundle exec ruby examples/graph_to_specs.rb

a real captured run

# generated from the plan's graph - one describe per task,
# examples dictated by each task's structural role

RSpec.describe "the pipeline" do
  describe "fetch orders" do  # root
    it "produces output from fixture input"  # roots own the boundary with the world
    it "raises a named error when the source is unreachable"
  end

  describe "fetch refunds" do  # root
    it "produces output from fixture input"  # roots own the boundary with the world
    it "raises a named error when the source is unreachable"
  end

  describe "build ledger" do  # join
    context "with all 2 inputs present" do
      it "combines sales and credits"
    end
    context "when sales is missing" do  # joins fail per-tributary, not vaguely
      it "reports which input was absent"
    end
    context "when credits is missing" do  # joins fail per-tributary, not vaguely
      it "reports which input was absent"
    end
  end

  describe "render report" do  # leaf
    it "transforms its upstream's output"  # assert on previous_output's shape
    it "produces the artifact consumers read"  # leaves are promises to the outside
  end

end

# 4 tasks -> 2 boundary suites, 1 join suites with per-tributary absence cases, 1 artifact suites.
# the graph decided what deserves a test; you decide what passes one.

source

# frozen_string_literal: true

# Graph to Specs: the plan's structure dictates its test plan - roots
# need fixture cases, joins need one case per missing tributary,
# leaves need output assertions. This generates the RSpec skeleton
# from the graph, so "what should we test?" stops being a staring
# contest with a blank file.
#
#   bundle exec ruby examples/graph_to_specs.rb
#
# Runs offline; prints a runnable-shaped spec skeleton.

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

def step(name)
  Agentic:class="y">:Task.new(description: name, agent_spec: {class="s">"name" => name, class="s">"instructions" => class="s">"work"})
end

orchestrator = Agentic:class="y">:PlanOrchestrator.new
orders = step(class="s">"fetch orders")
refunds = step(class="s">"fetch refunds")
ledger = step(class="s">"build ledger")
report = step(class="s">"render report")

orchestrator.add_task(orders)
orchestrator.add_task(refunds)
orchestrator.add_task(ledger, needs: {sales: orders, credits: refunds})
orchestrator.add_task(report, [ledger])

graph = orchestrator.graph
stats = graph[class="y">:stats]
names = graph[class="y">:tasks].transform_values(&class="y">:description)

puts class="s">"# generated from the plan's graph - one describe per task,"
puts class="s">"# examples dictated by each task's structural role"
puts
puts class="s">"RSpec.describe \"the pipeline\class="s">" do"

graph[class="y">:order].each do |id|
  name = names[id]
  deps = graph[class="y">:dependencies][id]
  labeled = graph[class="y">:edges].select { |e| e[class="y">:to] == id && e[class="y">:label] }
  role = []
  role << class="s">"root" if stats[class="y">:roots].include?(id)
  role << class="s">"join" if deps.size >= 2
  role << class="s">"leaf" if stats[class="y">:leaves].include?(id)

  puts class="s">"  describe \"#{name}\class="s">" do  # #{role.join(", class="s">")}"

  if stats[class="y">:roots].include?(id)
    puts class="s">"    it \"produces output from fixture input\class="s">"  # roots own the boundary with the world"
    puts class="s">"    it \"raises a named error when the source is unreachable\class="s">""
  end

  if deps.size >= 2
    puts class="s">"    context \"with all #{deps.size} inputs present\class="s">" do"
    puts class="s">"      it \"combines #{labeled.map { |e| e[class="y">:label] }.join(class="s">" and ")}\class="s">""
    puts class="s">"    end"
    labeled.each do |edge|
      puts class="s">"    context \"when #{edge[class="y">:label]} is missing\class="s">" do  # joins fail per-tributary, not vaguely"
      puts class="s">"      it \"reports which input was absent\class="s">""
      puts class="s">"    end"
    end
  elsif deps.size == 1
    puts class="s">"    it \"transforms its upstream's output\class="s">"  # assert on previous_output's shape"
  end

  if stats[class="y">:leaves].include?(id)
    puts class="s">"    it \"produces the artifact consumers read\class="s">"  # leaves are promises to the outside"
  end

  puts class="s">"  end"
  puts
end
puts class="s">"end"
puts
puts class="s">"# #{graph[class="y">:tasks].size} tasks -> #{stats[class="y">:roots].size} boundary suites, " \
  class="s">"#{graph[class="y">:dependencies].count { |_, d| d.size >= 2 }} join suites with " \
  class="s">"per-tributary absence cases, #{stats[class="y">:leaves].size} artifact suites."
puts class="s">"# the graph decided what deserves a test; you decide what passes one."