Commit 8d4423c

mo khan <mo@mokhan.ca>
2026-08-29 22:34:41
chore: tidy up
spec/evals/harness/ablator_spec.rb
@@ -1,74 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Elelem::Evals::Ablator do
-  # A case passes only while every required marker is still present in the prompt.
-  # This lets a test describe which lines are load-bearing purely by content.
-  def scorer_for(requirements)
-    lambda do |prompt|
-      results = requirements.map do |id, marker|
-        status = prompt.include?(marker) ? "PASS" : "FAIL"
-        Elelem::Evals::Result.new(
-          id: id, group: "g", status: status, failures: status == "PASS" ? [] : ["missing #{marker}"],
-          turns: 1, duration: 0.0, tools: [], response: ""
-        )
-      end
-      Elelem::Evals::Score.new(results: results)
-    end
-  end
-
-  subject(:ablator) { described_class.new(scorer_for: scorer_for(requirements)) }
-
-  describe "#minimize" do
-    let(:requirements) { { "keep" => "KEEP" } }
-
-    it "removes a line whose deletion regresses nothing" do
-      result = ablator.minimize("KEEP the case\nDEAD weight\n")
-
-      expect(result.prompt).to eq("KEEP the case\n")
-    end
-
-    it "keeps a load-bearing line" do
-      result = ablator.minimize("KEEP the case\n")
-
-      expect(result.prompt).to eq("KEEP the case\n")
-    end
-
-    it "reaches a fixpoint, dropping every dead line" do
-      result = ablator.minimize("dead one\nKEEP the case\ndead two\ndead three\n")
-
-      expect(result.prompt).to eq("KEEP the case\n")
-    end
-
-    it "never removes an ERB line even when it defends no case" do
-      result = ablator.minimize("<%= pwd %>\nDEAD weight\nKEEP the case\n")
-
-      expect(result.prompt).to eq("<%= pwd %>\nKEEP the case\n")
-    end
-
-    it "leaves blank lines in place" do
-      result = ablator.minimize("KEEP the case\n\nDEAD weight\n")
-
-      expect(result.prompt).to eq("KEEP the case\n\n")
-    end
-
-    context "with two load-bearing lines" do
-      let(:requirements) { { "keep" => "KEEP", "hold" => "HOLD" } }
-
-      it "keeps both and drops only the dead line" do
-        result = ablator.minimize("KEEP one\nDEAD weight\nHOLD two\n")
-
-        expect(result.prompt).to eq("KEEP one\nHOLD two\n")
-      end
-    end
-
-    describe "the defends map" do
-      let(:requirements) { { "keep" => "KEEP" } }
-
-      it "maps each surviving line to the cases that regress without it" do
-        result = ablator.minimize("KEEP the case\nDEAD weight\n")
-
-        expect(result.defends).to eq("KEEP the case\n" => ["g/keep"])
-      end
-    end
-  end
-end
spec/evals/harness/assertions_spec.rb
@@ -1,105 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Elelem::Evals::Assertions do
-  def failures(expect, workspace: nil, response: "", tools: [])
-    described_class.new(expect).failures(workspace: workspace, response: response, tools: tools)
-  end
-
-  describe "verify" do
-    it "passes when the command exits 0" do
-      Elelem::Evals::Workspace.open(fixture: "hello") do |workspace|
-        expect(failures({ verify: "true" }, workspace: workspace)).to be_empty
-      end
-    end
-
-    it "fails when the command exits non zero" do
-      Elelem::Evals::Workspace.open(fixture: "hello") do |workspace|
-        expect(failures({ verify: "false" }, workspace: workspace).first).to include("verify failed")
-      end
-    end
-  end
-
-  describe "files" do
-    it "fails when a required string is missing" do
-      Elelem::Evals::Workspace.open(fixture: "hello") do |workspace|
-        result = failures({ files: { "hello.rb" => { "contains" => ["Goodbye"] } } }, workspace: workspace)
-
-        expect(result.first).to include("hello.rb missing")
-      end
-    end
-
-    it "fails when a forbidden string is present" do
-      Elelem::Evals::Workspace.open(fixture: "hello") do |workspace|
-        result = failures({ files: { "hello.rb" => { "not_contains" => ["Hello"] } } }, workspace: workspace)
-
-        expect(result.first).to include("still contains")
-      end
-    end
-
-    it "fails when the file is missing" do
-      Elelem::Evals::Workspace.open(fixture: "hello") do |workspace|
-        result = failures({ files: { "nope.rb" => { "contains" => ["x"] } } }, workspace: workspace)
-
-        expect(result).to eq(["nope.rb missing"])
-      end
-    end
-  end
-
-  describe "response" do
-    it "passes when every string is present" do
-      expect(failures({ response_contains: %w[alpha beta] }, response: "alpha and beta")).to be_empty
-    end
-
-    it "fails when a string is absent" do
-      expect(failures({ response_contains: ["gamma"] }, response: "alpha").first).to include("response missing")
-    end
-
-    it "fails when a forbidden string is present" do
-      result = failures({ response_not_contains: [".git"] }, response: "the biggest file is .git/description")
-
-      expect(result.first).to include("response contains")
-    end
-
-    it "passes when no forbidden string is present" do
-      expect(failures({ response_not_contains: [".git"] }, response: "data/report.csv")).to be_empty
-    end
-
-    it "fails when a regex does not match" do
-      expect(failures({ response_matches: ['\d+'] }, response: "none").first).to include("does not match")
-    end
-  end
-
-  describe "tools" do
-    let(:tools) { [Elelem::Evals::ToolCall.new(name: "read", args: { "path" => "config/loader.rb" })] }
-
-    it "passes when a named tool was called" do
-      expect(failures({ tools_used: ["read"] }, tools: tools)).to be_empty
-    end
-
-    it "fails when a named tool was never called" do
-      expect(failures({ tools_used: ["write"] }, tools: tools).first).to include("never called")
-    end
-
-    it "passes when argument constraints match" do
-      expect(failures({ tools_used: [{ "read" => { "path" => "loader.rb" } }] }, tools: tools)).to be_empty
-    end
-
-    it "fails when argument constraints do not match" do
-      result = failures({ tools_used: [{ "read" => { "path" => "other.rb" } }] }, tools: tools)
-
-      expect(result.first).to include("never called")
-    end
-
-    it "fails when a forbidden tool was called" do
-      expect(failures({ tools_not_used: ["read"] }, tools: tools).first).to include("called read")
-    end
-  end
-
-  it "does not score turn count -- correctness only" do
-    expect(failures({ max_turns: 1 }, response: "anything")).to be_empty
-  end
-
-  it "reports every failure at once" do
-    expect(failures({ response_contains: %w[a b c] }, response: "").length).to eq(3)
-  end
-end
spec/evals/harness/bounded_client_spec.rb
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Elelem::Evals::BoundedClient do
-  let(:inner) { double("client", fetch: []) }
-
-  subject(:client) { described_class.new(inner, max_turns: 2) }
-
-  it "delegates to the wrapped client" do
-    client.fetch([{ role: "user", content: "hi" }])
-
-    expect(inner).to have_received(:fetch)
-  end
-
-  it "counts turns" do
-    2.times { client.fetch([]) }
-
-    expect(client.turns).to eq(2)
-  end
-
-  it "raises once past the limit" do
-    2.times { client.fetch([]) }
-
-    expect { client.fetch([]) }.to raise_error(described_class::TurnLimitExceeded)
-  end
-
-  it "reports the overrun" do
-    3.times { client.fetch([]) rescue nil }
-
-    expect(client.turns).to eq(3)
-  end
-end
spec/evals/harness/case_spec.rb
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Elelem::Evals::Case do
-  let(:yaml) do
-    <<~YAML
-      - id: patch-single-line
-        fixture: hello
-        setup:
-          - git init -q
-        turns:
-          - change the greeting to Goodbye
-        expect:
-          verify: ruby hello.rb
-          max_turns: 4
-      - id: no-expectations
-        fixture: hello
-        turns:
-          - say hello
-    YAML
-  end
-
-  let(:dir) { Dir.mktmpdir }
-  let(:file) do
-    path = File.join(dir, "editing.yml")
-    File.write(path, yaml)
-    path
-  end
-
-  after { FileUtils.remove_entry(dir) if File.directory?(dir) }
-
-  subject(:cases) { described_class.load_file(file) }
-
-  it "loads every case in the file" do
-    expect(cases.map(&:id)).to eq(%w[patch-single-line no-expectations])
-  end
-
-  it "takes the group from the file name" do
-    expect(cases.first.group).to eq("editing")
-  end
-
-  it "reads setup commands" do
-    expect(cases.first.setup).to eq(["git init -q"])
-  end
-
-  it "symbolizes the top level of expect" do
-    expect(cases.first.expect[:verify]).to eq("ruby hello.rb")
-  end
-
-  it "reads max_turns from expect" do
-    expect(cases.first.max_turns).to eq(4)
-  end
-
-  it "defaults max_turns to the generous safety ceiling" do
-    expect(cases.last.max_turns).to eq(30)
-  end
-
-  it "defaults setup and expect to empty" do
-    expect(cases.last.setup).to eq([])
-    expect(cases.last.expect).to eq({})
-  end
-end
spec/evals/harness/cases_lint_spec.rb
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe "eval cases are well formed" do
-  Elelem::Evals::Case.load_all.each do |kase|
-    describe kase.to_s do
-      it "references a fixture that exists" do
-        expect(File).to be_directory(File.join(Elelem::Evals::Workspace::FIXTURES, kase.fixture))
-      end
-
-      it "has at least one turn" do
-        expect(kase.turns).not_to be_empty
-      end
-
-      it "has at least one expectation" do
-        expect(kase.expect).not_to be_empty
-      end
-    end
-  end
-end
spec/evals/harness/improver_spec.rb
@@ -1,161 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Elelem::Evals::Improver do
-  # Replays a canned JSON body the way Ollama streams it.
-  class CannedClient
-    attr_reader :messages
-
-    def initialize(body)
-      @body = body
-    end
-
-    def fetch(messages, _tools = [], &block)
-      @messages = messages
-      block.call(type: "saying", text: @body)
-      []
-    end
-  end
-
-  let(:body) do
-    JSON.generate(
-      "analysis" => "the prompt never mentions git",
-      "changes" => [
-        { "old_text" => "# Policy", "new_text" => "# Git\nCommit with a 50 character subject.\n\n# Policy", "rationale" => "teach the 50/72 rule" }
-      ]
-    )
-  end
-
-  let(:client) { CannedClient.new(body) }
-  let(:prompt) { "Terminal coding agent.\n\n# Policy\n- Verify your work\n" }
-
-  subject(:improver) { described_class.new(client: client) }
-
-  describe "#plan" do
-    it "parses the model's JSON" do
-      plan = improver.plan(prompt: prompt, failures: [])
-
-      expect(plan["changes"].first["rationale"]).to eq("teach the 50/72 rule")
-    end
-
-    it "sends the prompt and the failures to the model" do
-      failure = Elelem::Evals::Result.new(
-        id: "commit-50-72", group: "git", status: "FAIL", failures: ["verify failed (exit 1)"],
-        turns: 3, duration: 1.0, tools: [], response: "committed"
-      )
-
-      improver.plan(prompt: prompt, failures: [failure])
-      sent = client.messages.map { |m| m[:content] }.join("\n")
-
-      expect(sent).to include("commit-50-72")
-      expect(sent).to include("verify failed")
-      expect(sent).to include("# Policy")
-    end
-
-    it "returns no changes when the model returns junk" do
-      plan = described_class.new(client: CannedClient.new("not json")).plan(prompt: prompt, failures: [])
-
-      expect(plan).to eq("analysis" => "", "changes" => [])
-    end
-
-    it "returns no changes when the model returns a JSON array" do
-      plan = described_class.new(client: CannedClient.new("[1, 2, 3]")).plan(prompt: prompt, failures: [])
-
-      expect(plan).to eq("analysis" => "", "changes" => [])
-    end
-
-    it "returns no changes when the model returns JSON null" do
-      plan = described_class.new(client: CannedClient.new("null")).plan(prompt: prompt, failures: [])
-
-      expect(plan).to eq("analysis" => "", "changes" => [])
-    end
-
-    it "returns no changes when changes is a JSON object" do
-      body = JSON.generate("analysis" => "x", "changes" => { "old_text" => "a", "new_text" => "b" })
-      plan = described_class.new(client: CannedClient.new(body)).plan(prompt: prompt, failures: [])
-
-      expect(plan["changes"]).to be_empty
-    end
-
-    it "does not share the changes array between fallback results" do
-      improver = described_class.new(client: CannedClient.new("not json"))
-
-      first = improver.plan(prompt: prompt, failures: [])
-      first["changes"] << "mutated"
-
-      expect(improver.plan(prompt: prompt, failures: [])["changes"]).to be_empty
-    end
-  end
-
-  describe "#apply" do
-    it "applies a change and reports the rationale" do
-      changes = [{ "old_text" => "# Policy", "new_text" => "# Git\n\n# Policy", "rationale" => "teach git" }]
-      updated, applied = improver.apply(prompt, changes)
-
-      expect(updated).to include("# Git")
-      expect(applied).to eq(["teach git"])
-    end
-
-    it "skips a change whose old_text is absent" do
-      changes = [{ "old_text" => "# Nowhere", "new_text" => "x", "rationale" => "nope" }]
-      updated, applied = improver.apply(prompt, changes)
-
-      expect(updated).to eq(prompt)
-      expect(applied).to be_empty
-    end
-
-    it "keeps regex escapes in new_text literal" do
-      changes = [{ "old_text" => "# Policy", "new_text" => "Use sed -i'' 's/a\\&b/c/' and \\0", "rationale" => "sed" }]
-      updated, = improver.apply(prompt, changes)
-
-      expect(updated).to include("s/a\\&b/c/' and \\0")
-    end
-
-    it "skips a change that is not an object" do
-      updated, applied = improver.apply(prompt, [["old_text", "# Policy"]])
-
-      expect(updated).to eq(prompt)
-      expect(applied).to be_empty
-    end
-
-    it "replaces only the first occurrence" do
-      changes = [{ "old_text" => "a", "new_text" => "b", "rationale" => "once" }]
-      updated, = improver.apply("a a a", changes)
-
-      expect(updated).to eq("b a a")
-    end
-
-    it "appends new_text to grow the prompt when old_text is empty" do
-      changes = [{ "old_text" => "", "new_text" => "# Git\nCommit small.", "rationale" => "add git" }]
-      updated, applied = improver.apply("Terminal coding agent.\n", changes)
-
-      expect(updated).to eq("Terminal coding agent.\n# Git\nCommit small.")
-      expect(applied).to eq(["add git"])
-    end
-
-    it "separates an appended section with a newline when the prompt lacks a trailing one" do
-      changes = [{ "old_text" => "", "new_text" => "more", "rationale" => "grow" }]
-      updated, = improver.apply("seed", changes)
-
-      expect(updated).to eq("seed\nmore")
-    end
-
-    it "grows from an empty prompt by appending" do
-      changes = [
-        { "old_text" => "", "new_text" => "line one", "rationale" => "first" },
-        { "old_text" => "", "new_text" => "line two", "rationale" => "second" }
-      ]
-      updated, applied = improver.apply("", changes)
-
-      expect(updated).to eq("line one\nline two")
-      expect(applied).to eq(["first", "second"])
-    end
-
-    it "ignores an empty old_text with empty new_text so it counts as no change" do
-      changes = [{ "old_text" => "", "new_text" => "", "rationale" => "noop" }]
-      updated, applied = improver.apply("seed", changes)
-
-      expect(updated).to eq("seed")
-      expect(applied).to be_empty
-    end
-  end
-end
spec/evals/harness/loop_spec.rb
@@ -1,202 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Elelem::Evals::Loop do
-  let(:dir) { Dir.mktmpdir }
-  let(:champion) { File.join(dir, "champion.erb") }
-  let(:cases) { [Elelem::Evals::Case.new(id: "a", group: "g", fixture: "hello", turns: ["hi"])] }
-  let(:out) { StringIO.new }
-  let(:no_preflight) { -> {} }
-  let(:changes) { [{ "old_text" => "original", "new_text" => "improved", "rationale" => "better" }] }
-
-  before { File.write(champion, "original prompt") }
-  after { FileUtils.remove_entry(dir) if File.directory?(dir) }
-
-  def result(id: "a", group: "g", status: "FAIL")
-    Elelem::Evals::Result.new(
-      id: id, group: group, status: status, failures: status == "PASS" ? [] : ["nope"],
-      turns: 1, duration: 0.1, tools: [], response: ""
-    )
-  end
-
-  def score(*results)
-    Elelem::Evals::Score.new(results: results)
-  end
-
-  def score_of(status)
-    score(result(status: status))
-  end
-
-  def scoring(by_prompt)
-    ->(prompt) { by_prompt.fetch(prompt) }
-  end
-
-  # Always proposes the same edit.
-  def improver_returning(changes)
-    instance_double(
-      Elelem::Evals::Improver,
-      plan: { "analysis" => "x", "changes" => changes },
-      apply: ["improved prompt", changes.map { |c| c["rationale"] }]
-    )
-  end
-
-  def build_loop(scorer_for:, improver: improver_returning(changes), preflight: no_preflight, budget: described_class::BUDGET)
-    described_class.new(
-      cases: cases, champion: champion, workdir: dir, improver: improver,
-      scorer_for: scorer_for, out: out, preflight: preflight, budget: budget
-    )
-  end
-
-  it "promotes a challenger that fixes a case" do
-    build_loop(scorer_for: scoring("original prompt" => score_of("FAIL"), "improved prompt" => score_of("PASS"))).run(rounds: 1)
-
-    expect(File.read(champion)).to eq("improved prompt")
-  end
-
-  it "rejects a challenger that regresses a case" do
-    build_loop(scorer_for: scoring("original prompt" => score_of("PASS"), "improved prompt" => score_of("FAIL"))).run(rounds: 1)
-
-    expect(File.read(champion)).to eq("original prompt")
-  end
-
-  it "stops early when everything already passes" do
-    improver = improver_returning(changes)
-
-    build_loop(improver: improver, scorer_for: ->(_prompt) { score_of("PASS") }).run(rounds: 3)
-
-    expect(improver).not_to have_received(:plan)
-  end
-
-  it "writes each challenger to its own file" do
-    build_loop(scorer_for: scoring("original prompt" => score_of("FAIL"), "improved prompt" => score_of("PASS"))).run(rounds: 1)
-
-    expect(File).to exist(File.join(dir, "challenger-1.erb"))
-    expect(File.read(File.join(dir, "challenger-1.erb"))).to eq("improved prompt")
-  end
-
-  it "keeps the champion unchanged and consumes every round when no change can ever be applied" do
-    improver = instance_double(
-      Elelem::Evals::Improver,
-      plan: { "analysis" => "x", "changes" => changes },
-      apply: ["improved prompt", []]
-    )
-
-    result = build_loop(improver: improver, scorer_for: ->(_prompt) { score_of("FAIL") }).run(rounds: 3)
-
-    expect(result).to be(false)
-    expect(File.read(champion)).to eq("original prompt")
-    expect(File).not_to exist(File.join(dir, "challenger-1.erb"))
-    expect(improver).to have_received(:plan).exactly(3).times
-  end
-
-  describe "size budget" do
-    it "keeps the champion when the challenger is over budget" do
-      loop = build_loop(scorer_for: ->(_prompt) { score_of("FAIL") }, budget: "improved prompt".length - 1)
-
-      expect(loop.run(rounds: 1)).to be(false)
-      expect(File.read(champion)).to eq("original prompt")
-    end
-
-    it "does not spend a scoring pass on a challenger that is over budget" do
-      scorer = lambda do |prompt|
-        raise "scored an over budget challenger" if prompt == "improved prompt"
-        score_of("FAIL")
-      end
-
-      expect { build_loop(scorer_for: scorer, budget: 5).run(rounds: 1) }.not_to raise_error
-    end
-
-    it "reports the size and the cap when rejecting" do
-      build_loop(scorer_for: ->(_prompt) { score_of("FAIL") }, budget: 5).run(rounds: 1)
-
-      expect(out.string).to include("template 15 chars, cap 5")
-    end
-
-    it "records an over budget rejection in history" do
-      build_loop(scorer_for: ->(_prompt) { score_of("FAIL") }, budget: 5).run(rounds: 1)
-
-      entry = JSON.parse(File.read(File.join(dir, "history.jsonl")).lines.first)
-
-      expect(entry).to include("promoted" => false, "reason" => "over_budget", "size" => 15)
-    end
-
-    it "reports the size change when promoting" do
-      build_loop(scorer_for: scoring("original prompt" => score_of("FAIL"), "improved prompt" => score_of("PASS"))).run(rounds: 1)
-
-      expect(out.string).to include("template 15 -> 15 chars")
-    end
-
-    it "prints the lines the promoted challenger changed" do
-      build_loop(scorer_for: scoring("original prompt" => score_of("FAIL"), "improved prompt" => score_of("PASS"))).run(rounds: 1)
-
-      expect(out.string).to include("- original prompt", "+ improved prompt")
-    end
-
-    it "warns when the champion itself is already over budget" do
-      build_loop(scorer_for: ->(_prompt) { score_of("PASS") }, budget: 5).run(rounds: 1)
-
-      expect(out.string).to include("over cap")
-    end
-  end
-
-  it "appends a round to history" do
-    build_loop(scorer_for: scoring("original prompt" => score_of("FAIL"), "improved prompt" => score_of("PASS"))).run(rounds: 1)
-
-    entry = JSON.parse(File.read(File.join(dir, "history.jsonl")).lines.first)
-
-    expect(entry).to include("round" => 1, "promoted" => true)
-  end
-
-  it "records cost in history" do
-    build_loop(scorer_for: scoring("original prompt" => score_of("FAIL"), "improved prompt" => score_of("PASS"))).run(rounds: 1)
-
-    entry = JSON.parse(File.read(File.join(dir, "history.jsonl")).lines.first)
-
-    expect(entry).to include("turns" => 1, "duration" => 0.1)
-  end
-
-  it "withholds holdout failures from the improver" do
-    holdout = result(id: "h", group: "holdout")
-    visible = result
-    improver = improver_returning(changes)
-
-    build_loop(improver: improver, scorer_for: ->(_p) { score(holdout, visible) }).run(rounds: 1)
-
-    expect(improver).to have_received(:plan).with(prompt: anything, failures: [visible])
-  end
-
-  it "does not promote a challenger that only improves a holdout case" do
-    baseline = score(result, result(id: "h", group: "holdout"))
-    holdout_only = score(result, result(id: "h", group: "holdout", status: "PASS"))
-
-    build_loop(scorer_for: scoring("original prompt" => baseline, "improved prompt" => holdout_only)).run(rounds: 1)
-
-    expect(File.read(champion)).to eq("original prompt")
-  end
-
-  it "does not promote a challenger that regresses a holdout case" do
-    baseline = score(result, result(id: "h", group: "holdout", status: "PASS"))
-    regressed = score(result(status: "PASS"), result(id: "h", group: "holdout"))
-
-    build_loop(scorer_for: scoring("original prompt" => baseline, "improved prompt" => regressed)).run(rounds: 1)
-
-    expect(File.read(champion)).to eq("original prompt")
-  end
-
-  it "sends the improver only one failure per case even when every repeat fails" do
-    fails = Array.new(3) { result }
-    improver = improver_returning(changes)
-
-    build_loop(improver: improver, scorer_for: ->(_p) { score(*fails) }).run(rounds: 1)
-
-    expect(improver).to have_received(:plan).with(prompt: anything, failures: [fails.first])
-  end
-
-  it "raises before scoring anything when the preflight check fails" do
-    loop = build_loop(
-      scorer_for: ->(_prompt) { raise "scoring should not have run" },
-      preflight: -> { raise "connection refused" }
-    )
-
-    expect { loop.run(rounds: 1) }.to raise_error(/connection refused/)
-  end
-end
spec/evals/harness/null_terminal_spec.rb
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Elelem::Evals::NullTerminal do
-  it "auto-allows tools the shipped rules would otherwise ask about" do
-    terminal = described_class.new
-
-    expect(terminal).not_to be_interactive
-    expect(Elelem::Permissions.new.check("execute", {}, terminal: terminal)).to be(true)
-  end
-end
spec/evals/harness/runner_spec.rb
@@ -1,90 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Elelem::Evals::Runner do
-  # Replies with a write tool call on the first fetch, then plain text.
-  class ScriptedClient
-    def initialize
-      @calls = 0
-    end
-
-    def fetch(_messages, _tools = [], &block)
-      @calls += 1
-      return first_turn(&block) if @calls == 1
-
-      block.call(type: "saying", text: "done, wrote goodbye.rb")
-      []
-    end
-
-    private
-
-    def first_turn(&block)
-      call = { id: "1", name: "write", arguments: { "path" => "goodbye.rb", "content" => "puts 'Goodbye'\n" } }
-      block.call(call.merge(type: "doing"))
-      [call]
-    end
-  end
-
-  let(:kase) do
-    Elelem::Evals::Case.new(
-      id: "writes-a-file",
-      group: "editing",
-      fixture: "hello",
-      turns: ["write goodbye.rb"],
-      expect: {
-        verify: "ruby goodbye.rb",
-        files: { "goodbye.rb" => { "contains" => ["Goodbye"] } },
-        response_contains: ["goodbye.rb"],
-        tools_used: [{ "write" => { "path" => "goodbye.rb" } }],
-        tools_not_used: ["read"],
-        max_turns: 5
-      }
-    )
-  end
-
-  subject(:runner) { described_class.new(prompt: "test prompt", client: -> { ScriptedClient.new }) }
-
-  it "passes a case the agent satisfies" do
-    result = runner.run(kase)
-
-    expect(result.failures).to be_empty
-    expect(result.status).to eq("PASS")
-  end
-
-  it "records the case identity" do
-    result = runner.run(kase)
-
-    expect(result.id).to eq("writes-a-file")
-    expect(result.group).to eq("editing")
-  end
-
-  it "records the tool calls" do
-    result = runner.run(kase)
-
-    expect(result.tools.map(&:name)).to eq(["write"])
-  end
-
-  it "counts turns" do
-    expect(runner.run(kase).turns).to eq(2)
-  end
-
-  it "fails a case whose assertions do not hold" do
-    failing = Elelem::Evals::Case.new(
-      id: "wants-more",
-      fixture: "hello",
-      turns: ["write goodbye.rb"],
-      expect: { response_contains: ["never said this"] }
-    )
-
-    result = runner.run(failing)
-
-    expect(result.status).to eq("FAIL")
-    expect(result.failures.first).to include("response missing")
-  end
-
-  it "leaves the working directory where it found it" do
-    before = Dir.pwd
-    runner.run(kase)
-
-    expect(Dir.pwd).to eq(before)
-  end
-end
spec/evals/harness/scorer_spec.rb
@@ -1,146 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Elelem::Evals::Scorer do
-  def result(id, status)
-    Elelem::Evals::Result.new(
-      id: id, group: "g", status: status, failures: status == "PASS" ? [] : ["nope"],
-      turns: 1, duration: 0.1, tools: [], response: ""
-    )
-  end
-
-  # Returns the statuses it was given, in order, one per run call.
-  class FakeRunner
-    def initialize(statuses)
-      @statuses = statuses.dup
-    end
-
-    def run(kase)
-      status = @statuses.shift
-      Elelem::Evals::Result.new(
-        id: kase.id, group: kase.group, status: status, failures: status == "PASS" ? [] : ["nope"],
-        turns: 1, duration: 0.1, tools: [], response: ""
-      )
-    end
-  end
-
-  let(:kase) { Elelem::Evals::Case.new(id: "a", group: "g", fixture: "hello", turns: ["hi"]) }
-
-  it "passes a case only when every repeat passes" do
-    scorer = described_class.new(runner: FakeRunner.new(%w[PASS PASS PASS]), repeat: 3)
-
-    expect(scorer.call([kase]).passed).to eq(1)
-  end
-
-  it "fails a case when any repeat fails" do
-    scorer = described_class.new(runner: FakeRunner.new(%w[PASS FAIL PASS]), repeat: 3)
-    score = scorer.call([kase])
-
-    expect(score.passed).to eq(0)
-    expect(score.failed).to eq(1)
-  end
-
-  it "keeps every result" do
-    scorer = described_class.new(runner: FakeRunner.new(%w[PASS PASS PASS]), repeat: 3)
-
-    expect(scorer.call([kase]).results.length).to eq(3)
-  end
-
-  # A single failure decides a case, so there is no reason to spend the remaining
-  # repeats -- this is what keeps a failing case cheap during overnight rounds.
-  class CountingRunner
-    attr_reader :calls
-
-    def initialize(statuses)
-      @statuses = statuses.dup
-      @calls = 0
-    end
-
-    def run(kase)
-      @calls += 1
-      status = @statuses.shift
-      Elelem::Evals::Result.new(
-        id: kase.id, group: kase.group, status: status, failures: status == "PASS" ? [] : ["nope"],
-        turns: 1, duration: 0.1, tools: [], response: ""
-      )
-    end
-  end
-
-  it "stops repeating a case as soon as one repeat fails" do
-    runner = CountingRunner.new(%w[FAIL PASS PASS])
-    described_class.new(runner: runner, repeat: 3).call([kase])
-
-    expect(runner.calls).to eq(1)
-  end
-
-  it "runs every repeat while the case keeps passing" do
-    runner = CountingRunner.new(%w[PASS PASS PASS])
-    described_class.new(runner: runner, repeat: 3).call([kase])
-
-    expect(runner.calls).to eq(3)
-  end
-
-  it "reports only failing results as failures" do
-    scorer = described_class.new(runner: FakeRunner.new(%w[PASS FAIL PASS]), repeat: 3)
-
-    expect(scorer.call([kase]).failures.length).to eq(1)
-  end
-
-  it "detects a regression against an earlier score" do
-    before = described_class.new(runner: FakeRunner.new(["PASS"]), repeat: 1).call([kase])
-    after = described_class.new(runner: FakeRunner.new(["FAIL"]), repeat: 1).call([kase])
-
-    expect(after.regressions_from(before)).to eq(["g/a"])
-  end
-
-  it "does not call an improvement a regression" do
-    before = described_class.new(runner: FakeRunner.new(["FAIL"]), repeat: 1).call([kase])
-    after = described_class.new(runner: FakeRunner.new(["PASS"]), repeat: 1).call([kase])
-
-    expect(after.regressions_from(before)).to be_empty
-  end
-
-  it "keeps cases with the same id in different groups apart" do
-    a = Elelem::Evals::Case.new(id: "x", group: "search", fixture: "hello", turns: ["hi"])
-    b = Elelem::Evals::Case.new(id: "x", group: "json", fixture: "hello", turns: ["hi"])
-    scorer = described_class.new(runner: FakeRunner.new(%w[PASS FAIL]), repeat: 1)
-
-    score = scorer.call([a, b])
-
-    expect(score.total).to eq(2)
-    expect(score.passed).to eq(1)
-  end
-
-  describe "cost" do
-    def counted(turns, duration)
-      Elelem::Evals::Result.new(
-        id: "a", group: "g", status: "PASS", failures: [], turns: turns, duration: duration, tools: [], response: ""
-      )
-    end
-
-    it "sums turns across results" do
-      score = Elelem::Evals::Score.new(results: [counted(2, 0.1), counted(3, 0.2)])
-
-      expect(score.turns).to eq(5)
-    end
-
-    it "sums duration across results" do
-      score = Elelem::Evals::Score.new(results: [counted(2, 0.1), counted(3, 0.25)])
-
-      expect(score.duration).to eq(0.35)
-    end
-  end
-
-  describe "#excluding" do
-    it "filters out every result from the given group" do
-      holdout = Elelem::Evals::Result.new(
-        id: "h", group: "holdout", status: "PASS", failures: [], turns: 1, duration: 0.1, tools: [], response: ""
-      )
-      score = Elelem::Evals::Score.new(results: [result("a", "PASS"), holdout])
-
-      excluded = score.excluding("holdout")
-
-      expect(excluded.total).to eq(1)
-      expect(excluded.results).to eq([result("a", "PASS")])
-    end
-  end
-end
spec/evals/harness/tasks_spec.rb
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe "Elelem::Evals tasks" do
-  let(:dir) { Dir.mktmpdir }
-  let(:out) { StringIO.new }
-
-  after { FileUtils.remove_entry(dir) if File.directory?(dir) }
-
-  describe ".minimize!" do
-    let(:champion) { File.join(dir, "champion.erb") }
-    let(:ablation) do
-      Elelem::Evals::Ablator::Ablation.new(prompt: "KEEP\n", defends: { "KEEP\n" => ["g/a"] })
-    end
-    let(:ablator) { instance_double(Elelem::Evals::Ablator, minimize: ablation) }
-
-    before { File.write(champion, "KEEP\nDEAD\n") }
-
-    it "writes the minimized prompt and the defends map" do
-      Elelem::Evals.minimize!(champion: champion, workdir: dir, ablator: ablator, out: out)
-
-      expect(File.read(File.join(dir, "minimized.erb"))).to eq("KEEP\n")
-      expect(JSON.parse(File.read(File.join(dir, "defends.json")))).to eq("KEEP\n" => ["g/a"])
-    end
-
-    it "minimizes the champion contents" do
-      Elelem::Evals.minimize!(champion: champion, workdir: dir, ablator: ablator, out: out)
-
-      expect(ablator).to have_received(:minimize).with("KEEP\nDEAD\n")
-    end
-  end
-end
spec/evals/harness/workspace_spec.rb
@@ -1,62 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Elelem::Evals::Workspace do
-  it "copies the fixture into a fresh directory" do
-    described_class.open(fixture: "hello") do |workspace|
-      expect(workspace.read("hello.rb")).to include("greet")
-    end
-  end
-
-  it "leaves the fixture free of harness files" do
-    described_class.open(fixture: "hello") do |workspace|
-      expect(Dir.children(workspace.path)).to contain_exactly("hello.rb")
-    end
-  end
-
-  it "runs setup commands in the workspace" do
-    described_class.open(fixture: "hello", setup: ["echo hi > setup.txt"]) do |workspace|
-      expect(workspace.read("setup.txt")).to eq("hi\n")
-    end
-  end
-
-  it "removes the directory afterwards" do
-    path = described_class.open(fixture: "hello", &:path)
-
-    expect(File).not_to exist(path)
-  end
-
-  it "strips bundler variables inside chdir" do
-    saved = ENV["BUNDLE_GEMFILE"]
-    ENV["BUNDLE_GEMFILE"] = "/somewhere/Gemfile"
-
-    described_class.open(fixture: "hello") do |workspace|
-      workspace.chdir { expect(ENV["BUNDLE_GEMFILE"]).to be_nil }
-    end
-
-    expect(ENV["BUNDLE_GEMFILE"]).to eq("/somewhere/Gemfile")
-  ensure
-    saved ? (ENV["BUNDLE_GEMFILE"] = saved) : ENV.delete("BUNDLE_GEMFILE")
-  end
-
-  it "returns nil for a file that does not exist" do
-    described_class.open(fixture: "hello") do |workspace|
-      expect(workspace.read("nope.rb")).to be_nil
-    end
-  end
-
-  it "raises SetupFailed when a setup command fails" do
-    expect do
-      described_class.open(fixture: "hello", setup: ["exit 1"]) { |workspace| workspace }
-    end.to raise_error(described_class::SetupFailed)
-  end
-
-  it "still removes the directory when a setup command fails" do
-    before = Dir.glob(File.join(Dir.tmpdir, "elelem-evals-*"))
-
-    expect do
-      described_class.open(fixture: "hello", setup: ["exit 1"]) { |workspace| workspace }
-    end.to raise_error(described_class::SetupFailed)
-
-    expect(Dir.glob(File.join(Dir.tmpdir, "elelem-evals-*"))).to eq(before)
-  end
-end
spec/evals/prompts/seed.erb
@@ -1,1 +0,0 @@
-<%= agents_md %>
spec/evals/support/ablator.rb
@@ -2,10 +2,6 @@
 
 module Elelem
   module Evals
-    # The "refactor" half of red-green-refactor: once the prompt is green, drop
-    # every line whose removal regresses nothing. What survives is the minimal
-    # set of lines, each load-bearing for some case. Deterministic; it never
-    # invents text, so it does not depend on a weak improver's judgement.
     class Ablator
       Ablation = Data.define(:prompt, :defends)
 
@@ -27,10 +23,6 @@ module Elelem
 
       private
 
-      # Scans every ablatable line, scoring its removal once. Returns as soon as a
-      # line is removable, as [[prompt_without_it, its_score], nil]. If none can be
-      # removed (a fixpoint), returns [nil, defends] where defends is the
-      # line -> cases-it-protects map the scan just built for free.
       def remove_one(prompt, score)
         lines = prompt.lines
         defends = {}
@@ -49,8 +41,6 @@ module Elelem
         [nil, defends]
       end
 
-      # ERB tags render the environment and blank lines carry the layout; neither
-      # defends a case, so exclude both from ablation.
       def ablatable?(line)
         !line.strip.empty? && !line.include?("<%")
       end
spec/evals/support/assertions.rb
@@ -7,9 +7,6 @@ module Elelem
         @expect = expect || {}
       end
 
-      # Scores correctness only -- files, response, tools. Turn count is NOT
-      # scored: max_turns is a generous safety ceiling (a hard stop against
-      # runaway loops), not a speed gate, so a correct-but-slow run still passes.
       def failures(workspace:, response:, tools:)
         [
           verify_failure(workspace),
spec/evals/support/case.rb
@@ -6,8 +6,6 @@ module Elelem
   module Evals
     class Case
       CASES = File.expand_path("../cases", __dir__)
-      # A generous safety ceiling so a runaway loop still stops, not a speed gate.
-      # Correctness (files/response/tools) decides pass/fail, not turn count.
       DEFAULT_MAX_TURNS = 30
 
       def self.load_all(dir = CASES)
spec/evals/support/client.rb
@@ -10,23 +10,19 @@ module Elelem
       Elelem::Net::Ollama.new(model: model, host: host, keep_alive: "30m", **params)
     end
 
-    # In-process GGUF model, run greedy (temp 0) for deterministic eval scoring.
-    # Memoized: the runner asks for a client per case, but generation is stateless
-    # (fresh context each call), so one resident model serves every case -- loading
-    # a fresh 4GB+ model per case would exhaust GPU/unified memory.
-    def self.gguf(model_path: ENV.fetch("GGUF_MODEL"))
+    def self.gguf(model: ENV.fetch("GGUF_MODEL"))
       @gguf ||= begin
         gpu = %w[vulkan cuda metal].include?(Elelem::Net::GGUF.backend)
         Elelem::Net::GGUF.new(
-          model_path: File.expand_path(model_path),
+          model: File.expand_path(model),
           n_ctx: Integer(ENV.fetch("GGUF_N_CTX", "8192")),
           n_gpu_layers: Integer(ENV.fetch("GGUF_N_GPU_LAYERS", gpu ? "999" : "0")),
-          temp: 0.0, seed: 42
+          temp: 0.0,
+          seed: 42
         )
       end
     end
 
-    # EVALS_PROVIDER=gguf points the runner at the local model instead of Ollama.
     def self.client(model: MODEL)
       return gguf if ENV["EVALS_PROVIDER"] == "gguf"
 
spec/evals/support/improver.rb
@@ -8,30 +8,18 @@ module Elelem
 
     class Improver
       SYSTEM = <<~PROMPT
-        You are a prompt engineer improving the system prompt of elelem, a terminal
-        coding agent. The agent's tools are: read(path), write(path, content),
-        edit(path, old, new), execute(command), grep(pattern), glob(pattern),
-        list(path), git(command), task(prompt) and verify(path).
-
-        You are shown the current prompt and the eval cases it failed. Suggest
-        targeted find/replace edits to the prompt that would fix those failures.
-
-        Rules:
-        - Each change must use an exact old_text string that appears in the prompt.
-        - To add a NEW section, set old_text to "" and put the whole section in
-          new_text; it is appended to the end of the prompt.
-        - Keep changes minimal. Do not rewrite whole sections.
-        - Do not remove or alter the ERB tags, they render the environment.
-        - Do not change what is already working.
-        - Focus on the highest impact failures first.
+        You are a prompt engineer improving the system prompt of a terminal coding agent.
+
+        You are shown the current prompt and the eval cases it failed. Suggest targeted find/replace edits to the prompt that would fix those failures.
 
         Respond with JSON in this exact format:
+
+        ```json
         {
           "analysis": "what is failing and why",
-          "changes": [
-            {"old_text": "exact text from the prompt", "new_text": "replacement", "rationale": "why this helps"}
-          ]
+          "changes": [{ "old_text": "exact text from the prompt", "new_text": "replacement", "rationale": "why this helps" }]
         }
+        ```
 
         Return an empty changes array if no edit would help.
       PROMPT
@@ -56,8 +44,6 @@ module Elelem
           old_text = change["old_text"].to_s
           new_text = change["new_text"].to_s
 
-          # An empty old_text means "grow the prompt": append the new section.
-          # This is what lets the loop build a prompt up from an empty seed.
           if old_text.empty?
             next text if new_text.empty?
 
@@ -68,7 +54,6 @@ module Elelem
           next text unless text.include?(old_text)
 
           applied << change["rationale"].to_s
-          # Block form, so \0 and \& in the model's text stay literal.
           text.sub(old_text) { new_text }
         end
 
spec/evals/support/runner.rb
@@ -10,11 +10,8 @@ module Elelem
       end
     end
 
-    # The prompt under test is the one elelem actually ships, not a copy that
-    # can drift from it. Challengers and history stay out of lib/.
     CHAMPION = File.expand_path("../../../lib/elelem/prompts/default.erb", __dir__)
     WORKDIR = File.expand_path("../prompts", __dir__)
-    SEED = File.join(WORKDIR, "seed.erb")
 
     class Runner
       def initialize(prompt:, client: -> { Evals.client })
spec/evals/support/scorer.rb
@@ -53,8 +53,6 @@ module Elelem
 
       private
 
-      # A case passes only if every repeat passes, so the first failure settles
-      # it -- stop early and spend no more runs on a case already known to fail.
       def runs_for(kase)
         results = []
         @repeat.times do
spec/evals/support/tasks.rb
@@ -21,7 +21,7 @@ module Elelem
       ablation
     end
 
-    def self.regenerate(rounds:, seed: SEED, workdir: WORKDIR, out: $stdout, loop_for: ->(champion) { Loop.new(champion: champion, out: out) })
+    def self.regenerate(rounds:, seed:, workdir: WORKDIR, out: $stdout, loop_for: ->(champion) { Loop.new(champion: champion, out: out) })
       FileUtils.mkdir_p(workdir)
       candidate = File.join(workdir, "candidate.erb")
       FileUtils.cp(seed, candidate)
spec/evals/cases_spec.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-RSpec.describe "eval cases", :eval do
+RSpec.describe "eval cases" do
   runner = Elelem::Evals::Runner.new(prompt: File.read(Elelem::Evals::CHAMPION))
 
   Elelem::Evals::Case.load_all.each do |kase|