143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
"""Tests for task add-self-improvement-loop.
|
|
|
|
Covers R1-R5 from tasks/add-self-improvement-loop/SPEC.md: install.sh and
|
|
update.sh wiring, template validation, and loop creation from template.
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_FRAMEWORK = Path.home() / ".automaton"
|
|
_INSTALL_SH = _FRAMEWORK / "scripts" / "install.sh"
|
|
_UPDATE_SH = _FRAMEWORK / "scripts" / "update.sh"
|
|
_STATUS_PY = _FRAMEWORK / "scripts" / "status.py"
|
|
_TEMPLATE = _FRAMEWORK / "templates" / "loops" / "self-improvement" / "loop.json"
|
|
|
|
_spec = importlib.util.spec_from_file_location("status_mod", _STATUS_PY)
|
|
st = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(st)
|
|
|
|
|
|
class TestInstallShWiring:
|
|
def test_install_sh_creates_self_improvement_loop(self):
|
|
content = _INSTALL_SH.read_text()
|
|
assert "--create-loop self-improvement" in content
|
|
assert "--from-template self-improvement" in content
|
|
|
|
def test_install_sh_schedules_self_improvement_loop(self):
|
|
content = _INSTALL_SH.read_text()
|
|
assert "--install-schedule self-improvement" in content
|
|
assert "--interval 3600" in content
|
|
|
|
def test_install_sh_has_opt_out_message(self):
|
|
content = _INSTALL_SH.read_text()
|
|
assert "--pause-loop self-improvement" in content
|
|
|
|
def test_install_sh_uses_framework_project(self):
|
|
content = _INSTALL_SH.read_text()
|
|
assert '--project "$FRAMEWORK_DIR"' in content
|
|
|
|
def test_install_sh_non_fatal_on_failure(self):
|
|
content = _INSTALL_SH.read_text()
|
|
assert "|| true" in content
|
|
|
|
|
|
class TestUpdateShWiring:
|
|
def test_update_sh_bootstraps_self_improvement_loop(self):
|
|
content = _UPDATE_SH.read_text()
|
|
assert "--create-loop self-improvement" in content
|
|
assert "--from-template self-improvement" in content
|
|
|
|
def test_update_sh_has_idempotent_check(self):
|
|
content = _UPDATE_SH.read_text()
|
|
assert "if [ ! -d" in content
|
|
assert "loops/self-improvement" in content
|
|
|
|
def test_update_sh_schedules_loop(self):
|
|
content = _UPDATE_SH.read_text()
|
|
assert "--install-schedule self-improvement" in content
|
|
|
|
def test_update_sh_non_fatal_on_failure(self):
|
|
content = _UPDATE_SH.read_text()
|
|
assert "|| true" in content
|
|
|
|
|
|
class TestSelfImprovementTemplate:
|
|
def test_template_has_audit_work_source(self):
|
|
cfg = json.loads(_TEMPLATE.read_text())
|
|
assert cfg["work_source"]["kind"] == "audit"
|
|
|
|
def test_template_has_correct_brakes(self):
|
|
cfg = json.loads(_TEMPLATE.read_text())
|
|
assert cfg["brakes"]["max_iterations"] == 10
|
|
assert cfg["brakes"]["score_plateau_window"] == 3
|
|
|
|
def test_template_has_worktree_enabled(self):
|
|
cfg = json.loads(_TEMPLATE.read_text())
|
|
assert cfg["blast_radius"]["use_worktree"] is True
|
|
|
|
def test_template_has_file_scope(self):
|
|
cfg = json.loads(_TEMPLATE.read_text())
|
|
scope = cfg["blast_radius"]["file_scope"]
|
|
assert "scripts/" in scope
|
|
assert "prompts/" in scope
|
|
assert "tests/" in scope
|
|
assert "design/" in scope
|
|
|
|
def test_template_has_role_prompts(self):
|
|
cfg = json.loads(_TEMPLATE.read_text())
|
|
roles = cfg["roles"]
|
|
assert roles["implement"]["prompt"] == "loop-implement.md"
|
|
assert roles["verify"]["prompt"] == "loop-verifier.md"
|
|
assert roles["orchestrate"]["prompt"] == "loop-orchestrate.md"
|
|
|
|
|
|
class TestCreateLoopFromTemplate:
|
|
def test_create_loop_self_improvement(self, tmp_path):
|
|
project = tmp_path / "fw"
|
|
(project / ".automaton").mkdir(parents=True)
|
|
|
|
class A:
|
|
pass
|
|
a = A()
|
|
a.create_loop = "self-improvement"
|
|
a.from_template = "self-improvement"
|
|
a.project = str(project)
|
|
|
|
rc = st.cmd_create_loop(a)
|
|
assert rc == 0
|
|
|
|
loop_dir = project / ".automaton" / "loops" / "self-improvement"
|
|
assert loop_dir.exists()
|
|
assert (loop_dir / "loop.json").exists()
|
|
assert (loop_dir / ".state.loop").exists()
|
|
assert (loop_dir / ".state.log").exists()
|
|
|
|
state = json.loads((loop_dir / ".state.loop").read_text())
|
|
assert state["status"] == "running"
|
|
assert state["name"] == "self-improvement"
|
|
|
|
cfg = json.loads((loop_dir / "loop.json").read_text())
|
|
assert cfg["name"] == "self-improvement"
|
|
assert cfg["work_source"]["kind"] == "audit"
|
|
|
|
def test_create_loop_idempotent_refuses_duplicate(self, tmp_path):
|
|
project = tmp_path / "fw"
|
|
(project / ".automaton").mkdir(parents=True)
|
|
|
|
class A:
|
|
pass
|
|
a = A()
|
|
a.create_loop = "self-improvement"
|
|
a.from_template = "self-improvement"
|
|
a.project = str(project)
|
|
|
|
assert st.cmd_create_loop(a) == 0
|
|
rc = st.cmd_create_loop(a)
|
|
assert rc == 2
|