Coverage for mcp_bridge/tools/continuous_loop.py: 18%

1"""

2 Continuous Loop (Ralph Loop) for Stravinsky.

3

4 Allows Stravinsky to operate in an autonomous loop until criteria are met.

5"""

6

7 import json

8 import logging

9 from pathlib import Path

10

11 logger = logging.getLogger(__name__)

12

13 async def enable_ralph_loop(goal: str, max_iterations: int = 10) -> str:

14 """

15 Enable continuous processing until a goal is met.

16

17 Args:

18 goal: The goal to achieve and verify.

19 max_iterations: Maximum number of iterations before stopping.

20 """

21 project_root = Path.cwd()

22 settings_file = project_root / ".claude" / "settings.json"

23

24 settings = {}

25 if settings_file.exists():

26 try:

27 settings = json.loads(settings_file.read_text())

28 except:

29 pass

30

31 if "hooks" not in settings:

32 settings["hooks"] = {}

33

34 # Set the Stop hook to re-trigger if goal not met

35 # Note: Stravinsky's prompt will handle the internal logic

36 # but the presence of this hook signals "Continue" to Claude Code.

37 settings["hooks"]["Stop"] = [

38 {

39 "type": "command",

40 "command": f'echo "Looping for goal: {goal}"',

41 }

42 ]

43

44 settings_file.parent.mkdir(parents=True, exist_ok=True)

45 settings_file.write_text(json.dumps(settings, indent=2))

46

47 return f"🔄 Ralph Loop ENABLED. Goal: {goal}. Stravinsky will now process continuously until completion."

48

49 async def disable_ralph_loop() -> str:

50 """Disable the autonomous loop."""

51 project_root = Path.cwd()

52 settings_file = project_root / ".claude" / "settings.json"

53

54 if not settings_file.exists():

55 return "Ralph Loop is already disabled."

56

57 try:

58 settings = json.loads(settings_file.read_text())

59 if "hooks" in settings and "Stop" in settings["hooks"]:

60 del settings["hooks"]["Stop"]

61 settings_file.write_text(json.dumps(settings, indent=2))

62 return "✅ Ralph Loop DISABLED."

63 except:

64 pass

65

66 return "Failed to disable Ralph Loop or it was not active."