ralph-code 0.5.0__tar.gz → 0.6.2__tar.gz
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_code-0.5.0/ralph_code.egg-info → ralph_code-0.6.2}/PKG-INFO +13 -4
- {ralph_code-0.5.0 → ralph_code-0.6.2}/README.md +12 -3
- {ralph_code-0.5.0 → ralph_code-0.6.2}/pyproject.toml +10 -1
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/__init__.py +1 -1
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/__main__.py +8 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/app.py +113 -11
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/config.py +46 -2
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/git_manager.py +8 -2
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/harness.py +8 -2
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/harness_runner.py +194 -43
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/prd_manager.py +19 -3
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/schemas/ralph_tasks_schema.json +2 -2
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/workflow.py +84 -20
- {ralph_code-0.5.0 → ralph_code-0.6.2/ralph_code.egg-info}/PKG-INFO +13 -4
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph_code.egg-info/SOURCES.txt +11 -1
- {ralph_code-0.5.0 → ralph_code-0.6.2}/setup.py +1 -1
- ralph_code-0.6.2/tests/test_app.py +796 -0
- ralph_code-0.6.2/tests/test_app_integration.py +825 -0
- ralph_code-0.6.2/tests/test_colors.py +155 -0
- ralph_code-0.6.2/tests/test_config.py +367 -0
- ralph_code-0.6.2/tests/test_git_manager.py +415 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/tests/test_harness.py +25 -6
- {ralph_code-0.5.0 → ralph_code-0.6.2}/tests/test_harness_runner.py +105 -9
- ralph_code-0.6.2/tests/test_prd_manager.py +435 -0
- ralph_code-0.6.2/tests/test_storage.py +210 -0
- ralph_code-0.6.2/tests/test_tasks.py +380 -0
- ralph_code-0.6.2/tests/test_user_stories.py +631 -0
- ralph_code-0.6.2/tests/test_workflow.py +470 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/LICENSE +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/MANIFEST.in +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/claude_runner.py +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/colors.py +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/schemas/task_schema.json +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/spinner.py +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/storage.py +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/tasks.py +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph/user_stories.py +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph_code.egg-info/dependency_links.txt +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph_code.egg-info/entry_points.txt +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph_code.egg-info/requires.txt +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/ralph_code.egg-info/top_level.txt +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/setup.cfg +0 -0
- {ralph_code-0.5.0 → ralph_code-0.6.2}/tests/test_spinner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ralph-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Automated task implementation with Claude Code and Codex
|
|
5
5
|
Author: Ralph Coding
|
|
6
6
|
License: MIT
|
|
@@ -37,7 +37,7 @@ Dynamic: requires-python
|
|
|
37
37
|
|
|
38
38
|
# ralph-code
|
|
39
39
|
|
|
40
|
-
Automated task implementation with Claude Code and Codex for "Ralph Coding". What is [Ralph Coding](https://ghuntley.com/ralph/)? It's a method of coding where context rot is avoided by controlling the retention of information. This method involves re-invoking claude or codex for each task, and passing information about the requirements, acceptance testing, and any progress that's made (or roadblocks/challenges faced) through files, rather than retaining all prompts + thinking + response tokens. It tends to result in more requests, some duplicated token work, but fairly consistent performance, and best of all it can largely be done unattended. Recommend Claude Max account or codex equivalent, but be aware that GPT-5 - GPT5.2's slow reasoning and response makes this ponderous, it's fine overnight.
|
|
40
|
+
Automated task implementation with Claude Code and Codex for "Ralph Coding". What is [Ralph Coding](https://ghuntley.com/ralph/)? It's a method of coding where context rot is avoided by controlling the retention of information. This method involves re-invoking claude or codex for each task, and passing information about the requirements, acceptance testing, and any progress that's made (or roadblocks/challenges faced) through files, rather than retaining all prompts + thinking + response tokens. It tends to result in more requests, some duplicated token work, but fairly consistent performance, and best of all it can largely be done unattended. Ralph now defaults to continuing past PRD-to-task conversion instead of pausing there, and non-interactive harness calls are bounded by timeouts and turn caps so stuck agent runs fail fast instead of hanging forever. Recommend Claude Max account or codex equivalent, but be aware that GPT-5 - GPT5.2's slow reasoning and response makes this ponderous, it's fine overnight.
|
|
41
41
|
|
|
42
42
|
Because LLMs are carrying out the work, we can specify a job of "Find all the python files in the project that directly or indirectly access sqlalchemy objects, and upgrade the code to work with sqlalchemy 2.* This will result in probably a single-task project, but that one task might add 50 other tasks (on per file) to the backlog, which are then processed sequentially."
|
|
43
43
|
|
|
@@ -59,6 +59,15 @@ pip install ralph-code
|
|
|
59
59
|
ralph [OPTIONS] [DIRECTORY]
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
## Recent changes
|
|
63
|
+
|
|
64
|
+
Version `0.6.2` includes:
|
|
65
|
+
- Bounded non-interactive harness execution with timeout and turn limits
|
|
66
|
+
- Structured `tasks.json` generation for more reliable PRD conversion
|
|
67
|
+
- Automatic continuation after task generation by default
|
|
68
|
+
- `PRDs/` as the standard task directory, with legacy `PRD/` compatibility
|
|
69
|
+
- Refreshed model catalogs and current defaults
|
|
70
|
+
|
|
62
71
|
### Options
|
|
63
72
|
|
|
64
73
|
- `--debug`: Enable debug logging, logs are saved into the .ralph subdirectory of the project
|
|
@@ -66,8 +75,8 @@ ralph [OPTIONS] [DIRECTORY]
|
|
|
66
75
|
|
|
67
76
|
## Usage
|
|
68
77
|
|
|
69
|
-
First create a task
|
|
70
|
-
Then you run
|
|
78
|
+
First create a task in `PRDs/`, give a short name for the task (used for the branch commits will be added to), and then give a description.
|
|
79
|
+
Then you run `ralph`, it will produce a `.md` file of the specifications, which will be broken into small tasks put into a `tasks.json` file. Each task will be worked on independently.
|
|
71
80
|
|
|
72
81
|
## Requirements
|
|
73
82
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ralph-code
|
|
2
2
|
|
|
3
|
-
Automated task implementation with Claude Code and Codex for "Ralph Coding". What is [Ralph Coding](https://ghuntley.com/ralph/)? It's a method of coding where context rot is avoided by controlling the retention of information. This method involves re-invoking claude or codex for each task, and passing information about the requirements, acceptance testing, and any progress that's made (or roadblocks/challenges faced) through files, rather than retaining all prompts + thinking + response tokens. It tends to result in more requests, some duplicated token work, but fairly consistent performance, and best of all it can largely be done unattended. Recommend Claude Max account or codex equivalent, but be aware that GPT-5 - GPT5.2's slow reasoning and response makes this ponderous, it's fine overnight.
|
|
3
|
+
Automated task implementation with Claude Code and Codex for "Ralph Coding". What is [Ralph Coding](https://ghuntley.com/ralph/)? It's a method of coding where context rot is avoided by controlling the retention of information. This method involves re-invoking claude or codex for each task, and passing information about the requirements, acceptance testing, and any progress that's made (or roadblocks/challenges faced) through files, rather than retaining all prompts + thinking + response tokens. It tends to result in more requests, some duplicated token work, but fairly consistent performance, and best of all it can largely be done unattended. Ralph now defaults to continuing past PRD-to-task conversion instead of pausing there, and non-interactive harness calls are bounded by timeouts and turn caps so stuck agent runs fail fast instead of hanging forever. Recommend Claude Max account or codex equivalent, but be aware that GPT-5 - GPT5.2's slow reasoning and response makes this ponderous, it's fine overnight.
|
|
4
4
|
|
|
5
5
|
Because LLMs are carrying out the work, we can specify a job of "Find all the python files in the project that directly or indirectly access sqlalchemy objects, and upgrade the code to work with sqlalchemy 2.* This will result in probably a single-task project, but that one task might add 50 other tasks (on per file) to the backlog, which are then processed sequentially."
|
|
6
6
|
|
|
@@ -22,6 +22,15 @@ pip install ralph-code
|
|
|
22
22
|
ralph [OPTIONS] [DIRECTORY]
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
## Recent changes
|
|
26
|
+
|
|
27
|
+
Version `0.6.2` includes:
|
|
28
|
+
- Bounded non-interactive harness execution with timeout and turn limits
|
|
29
|
+
- Structured `tasks.json` generation for more reliable PRD conversion
|
|
30
|
+
- Automatic continuation after task generation by default
|
|
31
|
+
- `PRDs/` as the standard task directory, with legacy `PRD/` compatibility
|
|
32
|
+
- Refreshed model catalogs and current defaults
|
|
33
|
+
|
|
25
34
|
### Options
|
|
26
35
|
|
|
27
36
|
- `--debug`: Enable debug logging, logs are saved into the .ralph subdirectory of the project
|
|
@@ -29,8 +38,8 @@ ralph [OPTIONS] [DIRECTORY]
|
|
|
29
38
|
|
|
30
39
|
## Usage
|
|
31
40
|
|
|
32
|
-
First create a task
|
|
33
|
-
Then you run
|
|
41
|
+
First create a task in `PRDs/`, give a short name for the task (used for the branch commits will be added to), and then give a description.
|
|
42
|
+
Then you run `ralph`, it will produce a `.md` file of the specifications, which will be broken into small tasks put into a `tasks.json` file. Each task will be worked on independently.
|
|
34
43
|
|
|
35
44
|
## Requirements
|
|
36
45
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ralph-code"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.2"
|
|
8
8
|
description = "Automated task implementation with Claude Code and Codex"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -57,6 +57,15 @@ include = ["ralph*"]
|
|
|
57
57
|
[tool.setuptools.package-data]
|
|
58
58
|
ralph = ["schemas/*.json"]
|
|
59
59
|
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
markers = [
|
|
62
|
+
"integration: marks tests as integration tests (may be slower)",
|
|
63
|
+
"e2e: marks tests as end-to-end tests (requires terminal)",
|
|
64
|
+
"slow: marks tests as slow (> 1 second)",
|
|
65
|
+
]
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
addopts = "-v"
|
|
68
|
+
|
|
60
69
|
[tool.mypy]
|
|
61
70
|
strict = true
|
|
62
71
|
python_version = "3.12"
|
|
@@ -7,8 +7,16 @@ import click
|
|
|
7
7
|
|
|
8
8
|
from .app import main
|
|
9
9
|
|
|
10
|
+
# Version is imported from pyproject.toml at build time
|
|
11
|
+
try:
|
|
12
|
+
from importlib.metadata import version
|
|
13
|
+
__version__ = version("ralph-code")
|
|
14
|
+
except Exception:
|
|
15
|
+
__version__ = "unknown"
|
|
16
|
+
|
|
10
17
|
|
|
11
18
|
@click.command()
|
|
19
|
+
@click.version_option(version=__version__, prog_name="ralph")
|
|
12
20
|
@click.option(
|
|
13
21
|
"--debug",
|
|
14
22
|
is_flag=True,
|
|
@@ -353,9 +353,19 @@ class RalphApp:
|
|
|
353
353
|
elif workflow.is_paused:
|
|
354
354
|
choices = [
|
|
355
355
|
{"name": "Resume workflow", "value": "resume"},
|
|
356
|
+
]
|
|
357
|
+
# If there's an error, offer retry option
|
|
358
|
+
if workflow.error_message:
|
|
359
|
+
choices.append({"name": "Retry", "value": "retry"})
|
|
360
|
+
# If paused after spec/tasks, offer review options
|
|
361
|
+
if workflow.current_task:
|
|
362
|
+
choices.append({"name": "Review PRD (open in editor)", "value": "review_prd"})
|
|
363
|
+
if has_stories:
|
|
364
|
+
choices.append({"name": "Review tasks.json (open in editor)", "value": "review_tasks"})
|
|
365
|
+
choices.extend([
|
|
356
366
|
{"name": "Add PRD", "value": "add"},
|
|
357
367
|
{"name": "List PRDs", "value": "list"},
|
|
358
|
-
]
|
|
368
|
+
])
|
|
359
369
|
if has_stories:
|
|
360
370
|
choices.append({"name": "Manage stories", "value": "stories"})
|
|
361
371
|
choices.extend([
|
|
@@ -440,6 +450,8 @@ class RalphApp:
|
|
|
440
450
|
{"name": f"Branch prefix: {self._config.branch_prefix}", "value": "branch_prefix"},
|
|
441
451
|
{"name": f"On error: {self._config.on_error}", "value": "on_error"},
|
|
442
452
|
{"name": f"Auto spec: {self._config.auto_spec_without_oversight}", "value": "auto_spec"},
|
|
453
|
+
{"name": f"Pause after PRD spec: {self._config.pause_after_spec}", "value": "pause_after_spec"},
|
|
454
|
+
{"name": f"Pause after tasks: {self._config.pause_after_tasks}", "value": "pause_after_tasks"},
|
|
443
455
|
{"name": f"Wait on rate limit: {self._config.wait_on_rate_limit}", "value": "rate_limit"},
|
|
444
456
|
{"name": f"Pause on completion: {self._config.pause_on_completion}", "value": "pause_completion"},
|
|
445
457
|
{"name": f"Always build tests: {self._config.always_build_tests}", "value": "tests"},
|
|
@@ -493,6 +505,12 @@ class RalphApp:
|
|
|
493
505
|
elif setting == "auto_spec":
|
|
494
506
|
self._config.auto_spec_without_oversight = not self._config.auto_spec_without_oversight
|
|
495
507
|
self.console.print(f"[cyan]Auto spec: {self._config.auto_spec_without_oversight}[/cyan]")
|
|
508
|
+
elif setting == "pause_after_spec":
|
|
509
|
+
self._config.pause_after_spec = not self._config.pause_after_spec
|
|
510
|
+
self.console.print(f"[cyan]Pause after PRD spec: {self._config.pause_after_spec}[/cyan]")
|
|
511
|
+
elif setting == "pause_after_tasks":
|
|
512
|
+
self._config.pause_after_tasks = not self._config.pause_after_tasks
|
|
513
|
+
self.console.print(f"[cyan]Pause after tasks: {self._config.pause_after_tasks}[/cyan]")
|
|
496
514
|
elif setting == "rate_limit":
|
|
497
515
|
self._config.wait_on_rate_limit = not self._config.wait_on_rate_limit
|
|
498
516
|
self.console.print(f"[cyan]Wait on rate limit: {self._config.wait_on_rate_limit}[/cyan]")
|
|
@@ -541,7 +559,7 @@ class RalphApp:
|
|
|
541
559
|
choices: list[Choice] = []
|
|
542
560
|
|
|
543
561
|
for model_name, label in supported_models:
|
|
544
|
-
choices.append(Choice(title=model_name, value=model_name))
|
|
562
|
+
choices.append(Choice(title=f"{model_name} ({label})", value=model_name))
|
|
545
563
|
|
|
546
564
|
# No models available - show alert and return
|
|
547
565
|
if not choices:
|
|
@@ -1057,6 +1075,60 @@ class RalphApp:
|
|
|
1057
1075
|
workflow.story_manager.update_story(story)
|
|
1058
1076
|
self.console.print(f"[{NORD14}]Removed: {removed}[/{NORD14}]")
|
|
1059
1077
|
|
|
1078
|
+
def _review_prd(self, workflow: WorkflowEngine) -> None:
|
|
1079
|
+
"""Open the current PRD in the default editor."""
|
|
1080
|
+
if not workflow.current_task:
|
|
1081
|
+
self.console.print("[dim]No PRD currently selected.[/dim]")
|
|
1082
|
+
return
|
|
1083
|
+
|
|
1084
|
+
prd_file = workflow.current_task.file_path
|
|
1085
|
+
self.console.print(f"Opening {prd_file} in default editor...")
|
|
1086
|
+
|
|
1087
|
+
import subprocess
|
|
1088
|
+
import platform
|
|
1089
|
+
|
|
1090
|
+
try:
|
|
1091
|
+
if platform.system() == "Darwin": # macOS
|
|
1092
|
+
subprocess.run(["open", str(prd_file)])
|
|
1093
|
+
elif platform.system() == "Windows":
|
|
1094
|
+
subprocess.run(["start", str(prd_file)], shell=True)
|
|
1095
|
+
else: # Linux
|
|
1096
|
+
subprocess.run(["xdg-open", str(prd_file)])
|
|
1097
|
+
|
|
1098
|
+
self.console.print("[green]✓ Opened in editor. Resume workflow when ready.[/green]")
|
|
1099
|
+
# Reload PRD after editing
|
|
1100
|
+
workflow.prd_manager.reload()
|
|
1101
|
+
except Exception as e:
|
|
1102
|
+
self.console.print(f"[red]Failed to open editor: {e}[/red]")
|
|
1103
|
+
self.console.print(f"[dim]File location: {prd_file}[/dim]")
|
|
1104
|
+
|
|
1105
|
+
def _review_tasks(self, workflow: WorkflowEngine) -> None:
|
|
1106
|
+
"""Open tasks.json in the default editor."""
|
|
1107
|
+
tasks_file = self.project_dir / "tasks.json"
|
|
1108
|
+
if not tasks_file.exists():
|
|
1109
|
+
self.console.print("[dim]No tasks.json file found.[/dim]")
|
|
1110
|
+
return
|
|
1111
|
+
|
|
1112
|
+
self.console.print(f"Opening {tasks_file} in default editor...")
|
|
1113
|
+
|
|
1114
|
+
import subprocess
|
|
1115
|
+
import platform
|
|
1116
|
+
|
|
1117
|
+
try:
|
|
1118
|
+
if platform.system() == "Darwin": # macOS
|
|
1119
|
+
subprocess.run(["open", str(tasks_file)])
|
|
1120
|
+
elif platform.system() == "Windows":
|
|
1121
|
+
subprocess.run(["start", str(tasks_file)], shell=True)
|
|
1122
|
+
else: # Linux
|
|
1123
|
+
subprocess.run(["xdg-open", str(tasks_file)])
|
|
1124
|
+
|
|
1125
|
+
self.console.print("[green]✓ Opened in editor. Resume workflow when ready.[/green]")
|
|
1126
|
+
# Reload tasks after editing
|
|
1127
|
+
workflow.story_manager.reload()
|
|
1128
|
+
except Exception as e:
|
|
1129
|
+
self.console.print(f"[red]Failed to open editor: {e}[/red]")
|
|
1130
|
+
self.console.print(f"[dim]File location: {tasks_file}[/dim]")
|
|
1131
|
+
|
|
1060
1132
|
def _show_answer_questions(self) -> None:
|
|
1061
1133
|
"""Show UI to answer PRD questions."""
|
|
1062
1134
|
workflow = self._get_workflow()
|
|
@@ -1167,6 +1239,17 @@ class RalphApp:
|
|
|
1167
1239
|
self._show_settings_menu()
|
|
1168
1240
|
elif choice == "run" or choice == "resume":
|
|
1169
1241
|
self._run_workflow()
|
|
1242
|
+
elif choice == "retry":
|
|
1243
|
+
# Reset iteration counter and run workflow again
|
|
1244
|
+
# resume() resets iteration to 0, giving full max_iterations again
|
|
1245
|
+
current_max = workflow._config.max_iterations
|
|
1246
|
+
self.console.print(f"[green]Retrying with {current_max} attempts...[/green]")
|
|
1247
|
+
workflow.resume()
|
|
1248
|
+
self._run_workflow()
|
|
1249
|
+
elif choice == "review_prd":
|
|
1250
|
+
self._review_prd(workflow)
|
|
1251
|
+
elif choice == "review_tasks":
|
|
1252
|
+
self._review_tasks(workflow)
|
|
1170
1253
|
elif choice == "pause":
|
|
1171
1254
|
workflow.pause()
|
|
1172
1255
|
self.console.print("[yellow]Paused[/yellow]")
|
|
@@ -1295,20 +1378,39 @@ class RalphApp:
|
|
|
1295
1378
|
self.console.print()
|
|
1296
1379
|
self._last_status_key = None # Reset change detection
|
|
1297
1380
|
|
|
1381
|
+
live = None
|
|
1298
1382
|
try:
|
|
1299
|
-
#
|
|
1300
|
-
|
|
1301
|
-
self.
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1383
|
+
# Moderate refresh rate for spinner animation (4 fps for better responsiveness)
|
|
1384
|
+
live = Live(
|
|
1385
|
+
self._build_live_status(),
|
|
1386
|
+
refresh_per_second=4,
|
|
1387
|
+
console=self.console,
|
|
1388
|
+
transient=False
|
|
1389
|
+
)
|
|
1390
|
+
live.start()
|
|
1391
|
+
self._live = live
|
|
1392
|
+
self._spinner.reset()
|
|
1393
|
+
|
|
1394
|
+
while workflow.step():
|
|
1395
|
+
# Update display for spinner animation
|
|
1396
|
+
live.update(self._build_live_status())
|
|
1397
|
+
|
|
1398
|
+
# Safety check: if workflow is paused or in error state, stop
|
|
1399
|
+
if workflow.is_paused or workflow.state == WorkflowState.ERROR:
|
|
1400
|
+
break
|
|
1307
1401
|
|
|
1308
1402
|
except KeyboardInterrupt:
|
|
1309
|
-
self._live = None
|
|
1310
1403
|
workflow.pause()
|
|
1404
|
+
if live:
|
|
1405
|
+
live.stop()
|
|
1406
|
+
self._live = None
|
|
1311
1407
|
self.console.print("\n[yellow]Paused by user[/yellow]")
|
|
1408
|
+
return
|
|
1409
|
+
|
|
1410
|
+
finally:
|
|
1411
|
+
if live:
|
|
1412
|
+
live.stop()
|
|
1413
|
+
self._live = None
|
|
1312
1414
|
|
|
1313
1415
|
# Show final status
|
|
1314
1416
|
if workflow.state == WorkflowState.IDLE:
|
|
@@ -14,9 +14,13 @@ DEFAULT_CONFIG = {
|
|
|
14
14
|
"harness": "claude",
|
|
15
15
|
"worker_model": "opus",
|
|
16
16
|
"summary_model": "haiku",
|
|
17
|
+
"harness_timeout_seconds": 1800,
|
|
18
|
+
"non_interactive_max_turns": 12,
|
|
17
19
|
"max_iterations": 10,
|
|
18
20
|
"max_story_attempts": 3,
|
|
19
21
|
"auto_spec_without_oversight": True,
|
|
22
|
+
"pause_after_spec": False,
|
|
23
|
+
"pause_after_tasks": False,
|
|
20
24
|
"wait_on_rate_limit": True,
|
|
21
25
|
"pause_on_completion": True,
|
|
22
26
|
"always_build_tests": False,
|
|
@@ -61,7 +65,7 @@ class Config:
|
|
|
61
65
|
if "worker_model" not in self._config:
|
|
62
66
|
harness = self._config.get("harness", DEFAULT_CONFIG["harness"])
|
|
63
67
|
if harness == "codex":
|
|
64
|
-
self._config["worker_model"] = "gpt-5.
|
|
68
|
+
self._config["worker_model"] = "gpt-5.3-codex"
|
|
65
69
|
else:
|
|
66
70
|
self._config["worker_model"] = "opus"
|
|
67
71
|
needs_save = True
|
|
@@ -70,7 +74,7 @@ class Config:
|
|
|
70
74
|
if "summary_model" not in self._config:
|
|
71
75
|
harness = self._config.get("harness", DEFAULT_CONFIG["harness"])
|
|
72
76
|
if harness == "codex":
|
|
73
|
-
self._config["summary_model"] = "gpt-5.
|
|
77
|
+
self._config["summary_model"] = "gpt-5.1-codex-mini"
|
|
74
78
|
else:
|
|
75
79
|
self._config["summary_model"] = "haiku"
|
|
76
80
|
needs_save = True
|
|
@@ -122,6 +126,26 @@ class Config:
|
|
|
122
126
|
self._config["summary_model"] = value
|
|
123
127
|
self._save()
|
|
124
128
|
|
|
129
|
+
@property
|
|
130
|
+
def harness_timeout_seconds(self) -> int:
|
|
131
|
+
"""Maximum seconds to wait for a harness subprocess before aborting."""
|
|
132
|
+
return int(self._config.get("harness_timeout_seconds", 1800))
|
|
133
|
+
|
|
134
|
+
@harness_timeout_seconds.setter
|
|
135
|
+
def harness_timeout_seconds(self, value: int) -> None:
|
|
136
|
+
self._config["harness_timeout_seconds"] = max(1, value)
|
|
137
|
+
self._save()
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def non_interactive_max_turns(self) -> int:
|
|
141
|
+
"""Maximum agent turns for non-interactive harness runs."""
|
|
142
|
+
return int(self._config.get("non_interactive_max_turns", 12))
|
|
143
|
+
|
|
144
|
+
@non_interactive_max_turns.setter
|
|
145
|
+
def non_interactive_max_turns(self, value: int) -> None:
|
|
146
|
+
self._config["non_interactive_max_turns"] = max(1, value)
|
|
147
|
+
self._save()
|
|
148
|
+
|
|
125
149
|
@property
|
|
126
150
|
def max_iterations(self) -> int:
|
|
127
151
|
"""Maximum iterations for implementation loop."""
|
|
@@ -142,6 +166,26 @@ class Config:
|
|
|
142
166
|
self._config["auto_spec_without_oversight"] = value
|
|
143
167
|
self._save()
|
|
144
168
|
|
|
169
|
+
@property
|
|
170
|
+
def pause_after_spec(self) -> bool:
|
|
171
|
+
"""Whether to pause after creating PRD specs for review."""
|
|
172
|
+
return bool(self._config["pause_after_spec"])
|
|
173
|
+
|
|
174
|
+
@pause_after_spec.setter
|
|
175
|
+
def pause_after_spec(self, value: bool) -> None:
|
|
176
|
+
self._config["pause_after_spec"] = value
|
|
177
|
+
self._save()
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def pause_after_tasks(self) -> bool:
|
|
181
|
+
"""Whether to pause after converting PRD to tasks for review."""
|
|
182
|
+
return bool(self._config["pause_after_tasks"])
|
|
183
|
+
|
|
184
|
+
@pause_after_tasks.setter
|
|
185
|
+
def pause_after_tasks(self, value: bool) -> None:
|
|
186
|
+
self._config["pause_after_tasks"] = value
|
|
187
|
+
self._save()
|
|
188
|
+
|
|
145
189
|
@property
|
|
146
190
|
def wait_on_rate_limit(self) -> bool:
|
|
147
191
|
"""Whether to wait and retry on rate limit."""
|
|
@@ -170,8 +170,9 @@ class GitManager:
|
|
|
170
170
|
"""Get list of changed files that should be staged (excluding ralph files)."""
|
|
171
171
|
result = self._run_git("status", "--porcelain")
|
|
172
172
|
files = []
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
# Use rstrip() to preserve leading spaces which are significant in porcelain format
|
|
174
|
+
for line in result.stdout.rstrip().split("\n"):
|
|
175
|
+
if not line.strip():
|
|
175
176
|
continue
|
|
176
177
|
# Status is first 2 chars, filename starts at position 3
|
|
177
178
|
filepath = line[3:].strip()
|
|
@@ -229,6 +230,11 @@ class GitManager:
|
|
|
229
230
|
|
|
230
231
|
return self.commit(message)
|
|
231
232
|
|
|
233
|
+
def get_staged_files(self) -> list[str]:
|
|
234
|
+
"""Get list of files currently staged for commit."""
|
|
235
|
+
result = self._run_git("diff", "--cached", "--name-only")
|
|
236
|
+
return [f for f in result.stdout.strip().split("\n") if f]
|
|
237
|
+
|
|
232
238
|
def get_unstaged_files(self) -> list[str]:
|
|
233
239
|
"""Get list of files with unstaged changes (modified + untracked).
|
|
234
240
|
|
|
@@ -46,13 +46,19 @@ HarnessType = Literal["claude", "codex", "custom"]
|
|
|
46
46
|
# These defaults are used when CLI model querying fails or isn't supported.
|
|
47
47
|
DEFAULT_MODELS: dict[HarnessType, list[tuple[str, str]]] = {
|
|
48
48
|
"claude": [
|
|
49
|
+
("default", "Standard"),
|
|
49
50
|
("haiku", "Light"),
|
|
50
51
|
("sonnet", "Standard"),
|
|
51
52
|
("opus", "Standard"),
|
|
53
|
+
("sonnet[1m]", "Extended"),
|
|
54
|
+
("opusplan", "Planning"),
|
|
52
55
|
],
|
|
53
56
|
"codex": [
|
|
54
57
|
("gpt-5.1-codex-mini", "Light"),
|
|
58
|
+
("gpt-5.3-codex-spark", "Preview"),
|
|
59
|
+
("gpt-5.3-codex", "Standard"),
|
|
55
60
|
("gpt-5.2-codex", "Standard"),
|
|
61
|
+
("gpt-5.1-codex", "Standard"),
|
|
56
62
|
("gpt-5.1-codex-max", "Standard"),
|
|
57
63
|
("gpt-5.2", "Standard"),
|
|
58
64
|
],
|
|
@@ -61,13 +67,13 @@ DEFAULT_MODELS: dict[HarnessType, list[tuple[str, str]]] = {
|
|
|
61
67
|
|
|
62
68
|
DEFAULT_WORKER_MODEL: dict[HarnessType, str] = {
|
|
63
69
|
"claude": "opus",
|
|
64
|
-
"codex": "gpt-5.
|
|
70
|
+
"codex": "gpt-5.3-codex",
|
|
65
71
|
"custom": "",
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
DEFAULT_SUMMARY_MODEL: dict[HarnessType, str] = {
|
|
69
75
|
"claude": "haiku",
|
|
70
|
-
"codex": "gpt-5.
|
|
76
|
+
"codex": "gpt-5.1-codex-mini",
|
|
71
77
|
"custom": "",
|
|
72
78
|
}
|
|
73
79
|
|