ralph-any 0.1.1__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ralph/__init__.py +3 -1
- ralph/cli.py +68 -19
- ralph/config.py +55 -0
- {ralph_any-0.1.1.dist-info → ralph_any-0.2.0.dist-info}/METADATA +1 -1
- ralph_any-0.2.0.dist-info/RECORD +12 -0
- ralph_any-0.1.1.dist-info/RECORD +0 -11
- {ralph_any-0.1.1.dist-info → ralph_any-0.2.0.dist-info}/WHEEL +0 -0
- {ralph_any-0.1.1.dist-info → ralph_any-0.2.0.dist-info}/entry_points.txt +0 -0
- {ralph_any-0.1.1.dist-info → ralph_any-0.2.0.dist-info}/licenses/LICENSE +0 -0
ralph/__init__.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Ralph Any — iterative AI dev loop via ACP."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.
|
|
3
|
+
__version__ = "0.2.0"
|
|
4
4
|
|
|
5
|
+
from ralph.config import load_config_file
|
|
5
6
|
from ralph.detect import detect_promise
|
|
6
7
|
from ralph.engine import LoopConfig, LoopResult, RalphEngine
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
10
|
"__version__",
|
|
11
|
+
"load_config_file",
|
|
10
12
|
"detect_promise",
|
|
11
13
|
"LoopConfig",
|
|
12
14
|
"LoopResult",
|
ralph/cli.py
CHANGED
|
@@ -7,7 +7,9 @@ import asyncio
|
|
|
7
7
|
import shlex
|
|
8
8
|
import sys
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
10
11
|
|
|
12
|
+
from ralph.config import load_config_file
|
|
11
13
|
from ralph.engine import LoopConfig, RalphEngine
|
|
12
14
|
|
|
13
15
|
EXIT_SUCCESS = 0
|
|
@@ -24,6 +26,9 @@ _STATE_TO_EXIT = {
|
|
|
24
26
|
"max_iterations": EXIT_MAX_ITERATIONS,
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
# Auto-detected prompt files, checked in order.
|
|
30
|
+
_AUTO_PROMPT_FILES = ("ralph.md", "TASK.md", "ralph.txt", "TASK.txt")
|
|
31
|
+
|
|
27
32
|
|
|
28
33
|
def _build_parser() -> argparse.ArgumentParser:
|
|
29
34
|
p = argparse.ArgumentParser(
|
|
@@ -32,38 +37,40 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
32
37
|
)
|
|
33
38
|
p.add_argument(
|
|
34
39
|
"prompt",
|
|
35
|
-
|
|
40
|
+
nargs="?",
|
|
41
|
+
default=None,
|
|
42
|
+
help="Task description, or path to a .md/.txt file (auto-detects ralph.md / TASK.md)",
|
|
36
43
|
)
|
|
37
44
|
p.add_argument(
|
|
38
45
|
"-m", "--max-iterations",
|
|
39
46
|
type=int,
|
|
40
|
-
default=
|
|
47
|
+
default=None,
|
|
41
48
|
help="Maximum loop iterations (default: 10)",
|
|
42
49
|
)
|
|
43
50
|
p.add_argument(
|
|
44
51
|
"-t", "--timeout",
|
|
45
52
|
type=int,
|
|
46
|
-
default=
|
|
53
|
+
default=None,
|
|
47
54
|
help="Maximum runtime in seconds (default: 1800 = 30m)",
|
|
48
55
|
)
|
|
49
56
|
p.add_argument(
|
|
50
57
|
"--promise",
|
|
51
|
-
default=
|
|
58
|
+
default=None,
|
|
52
59
|
help="Completion promise phrase (default: 任務完成!🥇)",
|
|
53
60
|
)
|
|
54
61
|
p.add_argument(
|
|
55
62
|
"-c", "--command",
|
|
56
|
-
default=
|
|
63
|
+
default=None,
|
|
57
64
|
help="ACP CLI command (default: claude-code-acp)",
|
|
58
65
|
)
|
|
59
66
|
p.add_argument(
|
|
60
67
|
"--command-args",
|
|
61
|
-
default=
|
|
68
|
+
default=None,
|
|
62
69
|
help="Extra arguments for the ACP CLI (e.g. '--experimental-acp')",
|
|
63
70
|
)
|
|
64
71
|
p.add_argument(
|
|
65
72
|
"-d", "--working-dir",
|
|
66
|
-
default=
|
|
73
|
+
default=None,
|
|
67
74
|
help="Working directory (default: .)",
|
|
68
75
|
)
|
|
69
76
|
p.add_argument(
|
|
@@ -82,6 +89,17 @@ def _resolve_prompt(raw: str) -> str:
|
|
|
82
89
|
return raw
|
|
83
90
|
|
|
84
91
|
|
|
92
|
+
def _auto_detect_prompt(working_dir: str) -> str | None:
|
|
93
|
+
"""Look for a default prompt file in the working directory."""
|
|
94
|
+
base = Path(working_dir)
|
|
95
|
+
for name in _AUTO_PROMPT_FILES:
|
|
96
|
+
p = base / name
|
|
97
|
+
if p.is_file():
|
|
98
|
+
print(f"📄 Auto-detected prompt file: {p}", flush=True)
|
|
99
|
+
return p.read_text(encoding="utf-8")
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
85
103
|
def _run(config: LoopConfig) -> int:
|
|
86
104
|
engine = RalphEngine(config)
|
|
87
105
|
try:
|
|
@@ -100,18 +118,49 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
100
118
|
parser = _build_parser()
|
|
101
119
|
args = parser.parse_args(argv)
|
|
102
120
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
# Layer 1: defaults
|
|
122
|
+
cfg: dict[str, Any] = {
|
|
123
|
+
"prompt": None,
|
|
124
|
+
"promise_phrase": "任務完成!🥇",
|
|
125
|
+
"command": "claude-code-acp",
|
|
126
|
+
"command_args": [],
|
|
127
|
+
"working_dir": ".",
|
|
128
|
+
"max_iterations": 10,
|
|
129
|
+
"timeout_seconds": 1800,
|
|
130
|
+
"dry_run": False,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Layer 2: ralph.yml (overrides defaults)
|
|
134
|
+
file_cfg = load_config_file(args.working_dir or ".")
|
|
135
|
+
if file_cfg:
|
|
136
|
+
cfg.update({k: v for k, v in file_cfg.items() if v is not None})
|
|
137
|
+
|
|
138
|
+
# Layer 3: CLI args (overrides config file)
|
|
139
|
+
if args.prompt is not None:
|
|
140
|
+
cfg["prompt"] = _resolve_prompt(args.prompt)
|
|
141
|
+
if args.max_iterations is not None:
|
|
142
|
+
cfg["max_iterations"] = args.max_iterations
|
|
143
|
+
if args.timeout is not None:
|
|
144
|
+
cfg["timeout_seconds"] = args.timeout
|
|
145
|
+
if args.promise is not None:
|
|
146
|
+
cfg["promise_phrase"] = args.promise
|
|
147
|
+
if args.command is not None:
|
|
148
|
+
cfg["command"] = args.command
|
|
149
|
+
if args.command_args is not None:
|
|
150
|
+
cfg["command_args"] = shlex.split(args.command_args)
|
|
151
|
+
if args.working_dir is not None:
|
|
152
|
+
cfg["working_dir"] = args.working_dir
|
|
153
|
+
if args.dry_run:
|
|
154
|
+
cfg["dry_run"] = True
|
|
155
|
+
|
|
156
|
+
# Auto-detect prompt file if no prompt given
|
|
157
|
+
if cfg["prompt"] is None:
|
|
158
|
+
cfg["prompt"] = _auto_detect_prompt(cfg["working_dir"])
|
|
159
|
+
|
|
160
|
+
if cfg["prompt"] is None:
|
|
161
|
+
parser.error("prompt is required (provide as argument or create ralph.md / TASK.md)")
|
|
162
|
+
|
|
163
|
+
config = LoopConfig(**cfg)
|
|
115
164
|
|
|
116
165
|
if config.dry_run:
|
|
117
166
|
print("Dry-run config:")
|
ralph/config.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Load ralph.yml config file."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shlex
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_config_file(working_dir: str) -> dict[str, Any] | None:
|
|
11
|
+
"""Load ralph.yml / ralph.yaml from *working_dir*. Returns None if absent."""
|
|
12
|
+
base = Path(working_dir)
|
|
13
|
+
for name in ("ralph.yml", "ralph.yaml"):
|
|
14
|
+
path = base / name
|
|
15
|
+
if path.is_file():
|
|
16
|
+
return _parse(path)
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _parse(path: Path) -> dict[str, Any]:
|
|
21
|
+
"""Parse a ralph config file (simple YAML subset, no dependency needed)."""
|
|
22
|
+
raw: dict[str, str] = {}
|
|
23
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
24
|
+
line = line.strip()
|
|
25
|
+
if not line or line.startswith("#"):
|
|
26
|
+
continue
|
|
27
|
+
if ":" not in line:
|
|
28
|
+
continue
|
|
29
|
+
key, _, value = line.partition(":")
|
|
30
|
+
raw[key.strip()] = value.strip()
|
|
31
|
+
|
|
32
|
+
cfg: dict[str, Any] = {}
|
|
33
|
+
|
|
34
|
+
_map = {
|
|
35
|
+
"command": "command",
|
|
36
|
+
"command_args": "command_args",
|
|
37
|
+
"promise": "promise_phrase",
|
|
38
|
+
"max_iterations": "max_iterations",
|
|
39
|
+
"timeout": "timeout_seconds",
|
|
40
|
+
"working_dir": "working_dir",
|
|
41
|
+
"prompt": "prompt",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for yaml_key, cfg_key in _map.items():
|
|
45
|
+
if yaml_key not in raw:
|
|
46
|
+
continue
|
|
47
|
+
val = raw[yaml_key]
|
|
48
|
+
if cfg_key in ("max_iterations", "timeout_seconds"):
|
|
49
|
+
cfg[cfg_key] = int(val)
|
|
50
|
+
elif cfg_key == "command_args":
|
|
51
|
+
cfg[cfg_key] = shlex.split(val)
|
|
52
|
+
else:
|
|
53
|
+
cfg[cfg_key] = val
|
|
54
|
+
|
|
55
|
+
return cfg
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ralph/__init__.py,sha256=fBtiHYjMPjo4atoDtuKfSI_efE7ai1nSPLDheW-8nFY,353
|
|
2
|
+
ralph/__main__.py,sha256=FdEO4lE2dw6-b4Kk97vznatvCAEuO-6qFwOkWP6en-c,69
|
|
3
|
+
ralph/cli.py,sha256=HrM3H4HUdkh_0wmqAcXSVMucI90cJ2JVR4gWt5YJvco,4877
|
|
4
|
+
ralph/config.py,sha256=H7jvtUwV9lBf3lo5AZg4AkJr3zMlK_C93ZXNElFuJcg,1573
|
|
5
|
+
ralph/detect.py,sha256=e99V2o9KECN2fR6sZ7qLghVpdomShCW1Jf_rHjo0og0,180
|
|
6
|
+
ralph/engine.py,sha256=CZFU7GtmKnPIXuN9mx-5iSxcUCyCgUNZkilj1POuTyM,4600
|
|
7
|
+
ralph/prompt.py,sha256=9B5z6zeEyX0Jr6tJ6P7_ahMUVckQySWST51DypGdIDQ,2022
|
|
8
|
+
ralph_any-0.2.0.dist-info/METADATA,sha256=MYi1ivYBwXk_yyYD_7xn40B9KeXvqoo5Hs2oWopfVUA,350
|
|
9
|
+
ralph_any-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
10
|
+
ralph_any-0.2.0.dist-info/entry_points.txt,sha256=39mDVcb7RNWfrI1JwWs7ae3TrLLzwzy6R6EurlGgTfI,41
|
|
11
|
+
ralph_any-0.2.0.dist-info/licenses/LICENSE,sha256=Losvcv3YtvA9lWEq2Ngj6bbejg6sBzR5vxZKb-Db5i0,1079
|
|
12
|
+
ralph_any-0.2.0.dist-info/RECORD,,
|
ralph_any-0.1.1.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
ralph/__init__.py,sha256=MhQ-PrDvbQk55q3i2yxFjFMR8rkJYFTTnz9bNpMORlA,287
|
|
2
|
-
ralph/__main__.py,sha256=FdEO4lE2dw6-b4Kk97vznatvCAEuO-6qFwOkWP6en-c,69
|
|
3
|
-
ralph/cli.py,sha256=1uxhTV8U2sw_p9qOM26KekNI5dh3lpLl_lcBpw6BSls,3179
|
|
4
|
-
ralph/detect.py,sha256=e99V2o9KECN2fR6sZ7qLghVpdomShCW1Jf_rHjo0og0,180
|
|
5
|
-
ralph/engine.py,sha256=CZFU7GtmKnPIXuN9mx-5iSxcUCyCgUNZkilj1POuTyM,4600
|
|
6
|
-
ralph/prompt.py,sha256=9B5z6zeEyX0Jr6tJ6P7_ahMUVckQySWST51DypGdIDQ,2022
|
|
7
|
-
ralph_any-0.1.1.dist-info/METADATA,sha256=Wvs53pyw1B3BOiwHfcT6KcFgP4mmyAMWYqfeWjcr7jU,350
|
|
8
|
-
ralph_any-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
9
|
-
ralph_any-0.1.1.dist-info/entry_points.txt,sha256=39mDVcb7RNWfrI1JwWs7ae3TrLLzwzy6R6EurlGgTfI,41
|
|
10
|
-
ralph_any-0.1.1.dist-info/licenses/LICENSE,sha256=Losvcv3YtvA9lWEq2Ngj6bbejg6sBzR5vxZKb-Db5i0,1079
|
|
11
|
-
ralph_any-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|