local describe, it, expect = lust.describe, lust.it, lust.expect
local compile_loop = require("compile_loop")
describe("mf_state initial fields (ST1)", function()
local state = compile_loop._test_make_mf_state()
it("file_digest is a table", function()
expect(type(state.file_digest)).to.equal("table")
end)
it("file_digest is empty on init", function()
local count = 0
for _ in pairs(state.file_digest) do count = count + 1 end
expect(count).to.equal(0)
end)
it("file_digest_refresh defaults to 'auto'", function()
expect(state.file_digest_refresh).to.equal("auto")
end)
it("iter starts at 0", function()
expect(state.iter).to.equal(0)
end)
it("last_err starts nil", function()
expect(state.last_err).to.equal(nil)
end)
it("sr_history is an empty table", function()
expect(type(state.sr_history)).to.equal("table")
local count = 0
for _ in pairs(state.sr_history) do count = count + 1 end
expect(count).to.equal(0)
end)
it("modified_set is an empty table", function()
expect(type(state.modified_set)).to.equal("table")
local count = 0
for _ in pairs(state.modified_set) do count = count + 1 end
expect(count).to.equal(0)
end)
end)
describe("is_stagnant_v2 full-window threshold (crux §1)", function()
local h = compile_loop._test_helpers()
local is_stagnant_v2 = h.is_stagnant_v2
local compute_sr_hash = h.compute_sr_hash
local update_state = h.update_state
local hA = compute_sr_hash("block_A")
local hB = compute_sr_hash("block_B")
it("returns false when sr_history has fewer than STAGNATION_WINDOW entries", function()
local state = compile_loop._test_make_mf_state()
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hA })
expect(is_stagnant_v2(state, true)).to.equal(false)
end)
it("returns false when 2-of-3 entries match (partial window, last_verify_failed=true)", function()
local state = compile_loop._test_make_mf_state()
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hB })
expect(is_stagnant_v2(state, true)).to.equal(false)
end)
it("returns false when last_verify_failed=false even with full-window identical hashes", function()
local state = compile_loop._test_make_mf_state()
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hA })
expect(is_stagnant_v2(state, false)).to.equal(false)
end)
it("returns true when all 3 entries in window are identical (full-window match)", function()
local state = compile_loop._test_make_mf_state()
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hA })
expect(is_stagnant_v2(state, true)).to.equal(true)
end)
it("uses only the last STAGNATION_WINDOW entries from sr_history", function()
local state = compile_loop._test_make_mf_state()
update_state(state, { sr_hash_append = hB })
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hA })
expect(is_stagnant_v2(state, true)).to.equal(true)
end)
it("[hA, hB, hA] — 2-of-3 non-contiguous match — returns false", function()
local state = compile_loop._test_make_mf_state()
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hB })
update_state(state, { sr_hash_append = hA })
expect(is_stagnant_v2(state, true)).to.equal(false)
end)
end)
describe("sr_history appended via update_state (crux §2)", function()
local h = compile_loop._test_helpers()
local compute_sr_hash = h.compute_sr_hash
local update_state = h.update_state
it("update_state appends sr_hash_append to sr_history", function()
local state = compile_loop._test_make_mf_state()
local hash = compute_sr_hash("some content")
update_state(state, { sr_hash_append = hash })
expect(#state.sr_history).to.equal(1)
expect(state.sr_history[1]).to.equal(hash)
end)
it("update_state appends multiple times preserving order", function()
local state = compile_loop._test_make_mf_state()
local hA = compute_sr_hash("content_A")
local hB = compute_sr_hash("content_B")
update_state(state, { sr_hash_append = hA })
update_state(state, { sr_hash_append = hB })
expect(#state.sr_history).to.equal(2)
expect(state.sr_history[1]).to.equal(hA)
expect(state.sr_history[2]).to.equal(hB)
end)
end)
describe("collect_modified_paths (crux §3)", function()
local h = compile_loop._test_helpers()
local collect_modified_paths = h.collect_modified_paths
it("returns empty list for empty set", function()
local result = collect_modified_paths({})
expect(type(result)).to.equal("table")
expect(#result).to.equal(0)
end)
it("returns sorted list of paths from set", function()
local set = { ["/b/file.lua"] = true, ["/a/file.lua"] = true }
local result = collect_modified_paths(set)
expect(#result).to.equal(2)
expect(result[1]).to.equal("/a/file.lua")
expect(result[2]).to.equal("/b/file.lua")
end)
it("each path appears exactly once", function()
local set = { ["/x/foo.lua"] = true }
local result = collect_modified_paths(set)
expect(#result).to.equal(1)
expect(result[1]).to.equal("/x/foo.lua")
end)
end)
if not log then
log = { warn = function() end, info = function() end, debug = function() end }
end
if not tool then
tool = { register = function() end }
end
if not std then
std = {
env = {
get = function(_name) return nil end,
get_or = function(_name, default) return default end,
},
json = { encode = function(v) return tostring(v) end },
}
end
describe("resolve_temperature — default 0.0 when env unset", function()
local h = compile_loop._test_helpers()
local resolve_temperature = h.resolve_temperature
it("returns 0.0 when no env override is set", function()
compile_loop._test_reset_env_get()
local t = resolve_temperature()
expect(t).to.equal(0.0)
end)
it("returns env value when COMPILE_LOOP_LLM_TEMPERATURE is '0.3'", function()
compile_loop._test_set_env_get(function(name)
if name == "COMPILE_LOOP_LLM_TEMPERATURE" then return "0.3" end
return nil
end)
local t = resolve_temperature()
compile_loop._test_reset_env_get()
expect(t > 0.29 and t < 0.31).to.equal(true)
end)
it("returns 0.0 and does not error on non-numeric env value", function()
compile_loop._test_set_env_get(function(name)
if name == "COMPILE_LOOP_LLM_TEMPERATURE" then return "not_a_number" end
return nil
end)
local t = resolve_temperature()
compile_loop._test_reset_env_get()
expect(t).to.equal(0.0)
end)
it("returns 0.0 when env returns nil", function()
compile_loop._test_set_env_get(function(_name) return nil end)
local t = resolve_temperature()
compile_loop._test_reset_env_get()
expect(t).to.equal(0.0)
end)
end)
describe("temperature in OpenAI body via _test_set_llm_call capture", function()
local h_temp = compile_loop._test_helpers()
local resolve_temperature = h_temp.resolve_temperature
local function capture_temperature(caller_temp)
local captured_temperature = nil
compile_loop._test_set_llm_call(function(opts, _msgs)
captured_temperature = opts.temperature or resolve_temperature()
return {
choices = { {
message = {
content = "```lua\nprint('hi')\n```",
role = "assistant",
},
} },
}
end)
local tmp = "/tmp/cl_temp_test_" .. tostring(os.time()) .. ".lua"
local f = io.open(tmp, "w")
if f then f:write("-- placeholder\n"); f:close() end
local conf = {
target_files = { tmp },
multi_file = false,
edit_mode = "full",
lang = "lua",
spec = "test",
runner = function(_path) return { ok = true } end,
max_iters = 5,
}
if caller_temp ~= nil then
conf.temperature = caller_temp
end
local run_loop_fn = compile_loop._test_helpers().run_loop
run_loop_fn(conf)
compile_loop._test_reset_llm_call()
os.remove(tmp)
return captured_temperature
end
it("temperature defaults to 0.0 when caller and env are both unset", function()
compile_loop._test_reset_env_get()
local t = capture_temperature(nil)
expect(t).to.equal(0.0)
end)
it("caller temperature=0.5 overrides env and default", function()
compile_loop._test_set_env_get(function(name)
if name == "COMPILE_LOOP_LLM_TEMPERATURE" then return "0.3" end
return nil
end)
local t = capture_temperature(0.5)
compile_loop._test_reset_env_get()
expect(t > 0.49 and t < 0.51).to.equal(true)
end)
it("env COMPILE_LOOP_LLM_TEMPERATURE=0.3 is used when caller unset", function()
compile_loop._test_set_env_get(function(name)
if name == "COMPILE_LOOP_LLM_TEMPERATURE" then return "0.3" end
return nil
end)
local t = capture_temperature(nil)
compile_loop._test_reset_env_get()
expect(t > 0.29 and t < 0.31).to.equal(true)
end)
end)
local h_run = compile_loop._test_helpers()
local run_loop = h_run.run_loop
local function make_run_conf(opts)
local tmp = "/tmp/cl_rl_" .. tostring(os.time()) .. math.random(1000) .. ".lua"
local f = io.open(tmp, "w")
if f then f:write("print('placeholder')\n"); f:close() end
local conf = {
target_files = { tmp },
multi_file = false,
edit_mode = "full",
lang = "lua",
spec = "test",
runner = opts.runner or function(_path) return { ok = false, stderr = "fail", exit_code = 1 } end,
max_iters = opts.max_iters or 10,
_tmp_path = tmp, }
return conf
end
local function run_bad_stagnation(max_iters)
compile_loop._test_set_llm_call(function(_opts, _msgs)
return { choices = { { message = { content = "", role = "assistant" } } } }
end)
local conf = make_run_conf({ max_iters = max_iters })
local result = run_loop(conf)
compile_loop._test_reset_llm_call()
os.remove(conf._tmp_path)
return result
end
local function run_good_stagnation()
compile_loop._test_set_llm_call(function(_opts, _msgs)
return { choices = { { message = { content = "```lua\nprint('x')\n```", role = "assistant" } } } }
end)
local conf = make_run_conf({
runner = function(_path)
return { ok = false, stderr = "same_error_always", exit_code = 1 }
end,
max_iters = 10,
})
local result = run_loop(conf)
compile_loop._test_reset_llm_call()
os.remove(conf._tmp_path)
return result
end
describe("bad stagnation — no_edits_applied BLOCKED after STAGNATION_WINDOW (3) iters", function()
it("failure_reason is no_edits_applied after 3 consecutive zero-edit iters", function()
local result = run_bad_stagnation(10)
expect(result.ok).to.equal(false)
expect(result.failure_reason).to.equal("no_edits_applied")
end)
it("terminates at or before iter 3 (STAGNATION_WINDOW), not max_iters", function()
local result = run_bad_stagnation(10)
expect(result.iters <= 3).to.equal(true)
end)
it("result.ok is false", function()
local result = run_bad_stagnation(10)
expect(result.ok).to.equal(false)
end)
end)
describe("bad stagnation reset — count resets on successful edit", function()
it("failure_reason is no_edits_applied after another 3 bad iters post-reset", function()
local call_n = 0
local seq = { true, true, false, true, true, true }
compile_loop._test_set_llm_call(function(_opts, _msgs)
call_n = call_n + 1
local is_empty = (seq[call_n] ~= false)
if is_empty then
return { choices = { { message = { content = "", role = "assistant" } } } }
else
return { choices = { { message = { content = "```lua\nprint('hi')\n```", role = "assistant" } } } }
end
end)
local runner_n = 0
local conf = make_run_conf({
runner = function(_path)
runner_n = runner_n + 1
local stderr = (runner_n == 3) and "different_stderr" or "fail"
return { ok = false, stderr = stderr, exit_code = 1 }
end,
max_iters = 20,
})
local result = run_loop(conf)
compile_loop._test_reset_llm_call()
os.remove(conf._tmp_path)
expect(result.ok).to.equal(false)
expect(result.failure_reason).to.equal("no_edits_applied")
expect(result.iters <= 6).to.equal(true)
end)
end)
describe("good stagnation — failure_reason=stagnation preserved when edits succeed", function()
it("is_stagnant fires with failure_reason=stagnation when code is non-empty each iter", function()
local result = run_good_stagnation()
expect(result.ok).to.equal(false)
expect(result.failure_reason).to.equal("stagnation")
end)
end)