ralph-cli 2.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 -0
- ralph/cli.py +61 -0
- ralph/commands/__init__.py +11 -0
- ralph/commands/init_cmd.py +358 -0
- ralph/commands/loop.py +725 -0
- ralph/commands/once.py +239 -0
- ralph/commands/prd.py +281 -0
- ralph/commands/review.py +330 -0
- ralph/commands/sync.py +137 -0
- ralph/commands/tasks.py +507 -0
- ralph/models/__init__.py +51 -0
- ralph/models/config.py +97 -0
- ralph/models/finding.py +134 -0
- ralph/models/manifest.py +77 -0
- ralph/models/review_state.py +97 -0
- ralph/models/reviewer.py +165 -0
- ralph/models/tasks.py +96 -0
- ralph/services/__init__.py +53 -0
- ralph/services/claude.py +360 -0
- ralph/services/fix_loop.py +294 -0
- ralph/services/git.py +177 -0
- ralph/services/language.py +96 -0
- ralph/services/review_loop.py +461 -0
- ralph/services/reviewer_config_writer.py +136 -0
- ralph/services/reviewer_detector.py +147 -0
- ralph/services/scaffold.py +531 -0
- ralph/services/skill_loader.py +185 -0
- ralph/services/skills.py +520 -0
- ralph/skills/REVIEWER_TEMPLATE.md +228 -0
- ralph/skills/SKILL_TEMPLATE.md +192 -0
- ralph/skills/__init__.py +5 -0
- ralph/skills/ralph/__init__.py +1 -0
- ralph/skills/ralph/iteration/SKILL.md +210 -0
- ralph/skills/ralph/iteration/__init__.py +1 -0
- ralph/skills/ralph/prd/SKILL.md +201 -0
- ralph/skills/ralph/prd/__init__.py +1 -0
- ralph/skills/ralph/tasks/SKILL.md +250 -0
- ralph/skills/ralph/tasks/__init__.py +1 -0
- ralph/skills/reviewers/__init__.py +1 -0
- ralph/skills/reviewers/code_simplifier/SKILL.md +298 -0
- ralph/skills/reviewers/code_simplifier/__init__.py +1 -0
- ralph/skills/reviewers/github_actions/SKILL.md +333 -0
- ralph/skills/reviewers/github_actions/__init__.py +1 -0
- ralph/skills/reviewers/language/__init__.py +1 -0
- ralph/skills/reviewers/language/bicep/SKILL.md +394 -0
- ralph/skills/reviewers/language/bicep/__init__.py +1 -0
- ralph/skills/reviewers/language/python/SKILL.md +363 -0
- ralph/skills/reviewers/language/python/__init__.py +1 -0
- ralph/skills/reviewers/release/SKILL.md +323 -0
- ralph/skills/reviewers/release/__init__.py +1 -0
- ralph/skills/reviewers/repo_structure/SKILL.md +295 -0
- ralph/skills/reviewers/repo_structure/__init__.py +1 -0
- ralph/skills/reviewers/test_quality/SKILL.md +498 -0
- ralph/skills/reviewers/test_quality/__init__.py +1 -0
- ralph/utils/__init__.py +39 -0
- ralph/utils/console.py +126 -0
- ralph/utils/files.py +111 -0
- ralph/utils/prompt.py +44 -0
- ralph_cli-2.2.0.dist-info/METADATA +280 -0
- ralph_cli-2.2.0.dist-info/RECORD +63 -0
- ralph_cli-2.2.0.dist-info/WHEEL +4 -0
- ralph_cli-2.2.0.dist-info/entry_points.txt +2 -0
- ralph_cli-2.2.0.dist-info/licenses/LICENSE +21 -0
ralph/__init__.py
ADDED
ralph/cli.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Ralph CLI entry point.
|
|
2
|
+
|
|
3
|
+
This module provides the main entry point for the Ralph CLI application,
|
|
4
|
+
a tool for autonomous iteration patterns with Claude Code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from ralph import __version__
|
|
12
|
+
from ralph.commands import init, loop, once, prd, review, sync, tasks
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(
|
|
17
|
+
name="ralph",
|
|
18
|
+
help="Ralph CLI - Autonomous iteration pattern for Claude Code",
|
|
19
|
+
no_args_is_help=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def version_callback(value: bool) -> None:
|
|
24
|
+
"""Handle the version flag callback.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
value: Whether the version flag was provided.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
typer.Exit: Always raised after printing version when value is True.
|
|
31
|
+
"""
|
|
32
|
+
if value:
|
|
33
|
+
typer.echo(f"ralph {__version__}")
|
|
34
|
+
raise typer.Exit()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.callback()
|
|
38
|
+
def main(
|
|
39
|
+
version: bool | None = typer.Option(
|
|
40
|
+
None,
|
|
41
|
+
"--version",
|
|
42
|
+
"-V",
|
|
43
|
+
callback=version_callback,
|
|
44
|
+
is_eager=True,
|
|
45
|
+
help="Show version and exit.",
|
|
46
|
+
),
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Ralph CLI - Autonomous iteration pattern for Claude Code."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
app.command(name="init", help="Scaffold a project for Ralph workflow")(init)
|
|
52
|
+
app.command(name="prd", help="Create a PRD interactively with Claude")(prd)
|
|
53
|
+
app.command(name="tasks", help="Convert a specification file to TASKS.json")(tasks)
|
|
54
|
+
app.command(name="once", help="Execute a single Ralph iteration")(once)
|
|
55
|
+
app.command(name="loop", help="Run multiple Ralph iterations automatically")(loop)
|
|
56
|
+
app.command(name="sync", help="Sync Ralph skills to Claude Code")(sync)
|
|
57
|
+
app.command(name="review", help="Run the review loop with automatic configuration")(review)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
app()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Ralph CLI commands."""
|
|
2
|
+
|
|
3
|
+
from ralph.commands.init_cmd import init
|
|
4
|
+
from ralph.commands.loop import loop
|
|
5
|
+
from ralph.commands.once import once
|
|
6
|
+
from ralph.commands.prd import prd
|
|
7
|
+
from ralph.commands.review import review
|
|
8
|
+
from ralph.commands.sync import sync
|
|
9
|
+
from ralph.commands.tasks import tasks
|
|
10
|
+
|
|
11
|
+
__all__ = ["init", "prd", "tasks", "once", "loop", "sync", "review"]
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""Ralph init command - scaffold a project for Ralph workflow.
|
|
2
|
+
|
|
3
|
+
This module implements the 'ralph init' command which creates
|
|
4
|
+
the plans directory structure and configuration files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import subprocess
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from rich.prompt import Confirm
|
|
13
|
+
|
|
14
|
+
from ralph.commands.prd import prd as prd_command
|
|
15
|
+
from ralph.services import ClaudeError, ClaudeService, ProjectType, ScaffoldService
|
|
16
|
+
from ralph.utils import console, print_success, print_warning
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _is_git_repo(project_root: Path) -> bool:
|
|
22
|
+
"""Check if the directory is inside a git repository.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
project_root: The directory to check.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
True if inside a git repo, False otherwise.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
result = subprocess.run(
|
|
32
|
+
["git", "rev-parse", "--git-dir"],
|
|
33
|
+
cwd=project_root,
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
check=False,
|
|
37
|
+
)
|
|
38
|
+
return result.returncode == 0
|
|
39
|
+
except FileNotFoundError:
|
|
40
|
+
# git not installed
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _init_git_repo(project_root: Path) -> bool:
|
|
45
|
+
"""Initialize a new git repository.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
project_root: The directory to initialize.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
True if successful, False otherwise.
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
result = subprocess.run(
|
|
55
|
+
["git", "init"],
|
|
56
|
+
cwd=project_root,
|
|
57
|
+
capture_output=True,
|
|
58
|
+
text=True,
|
|
59
|
+
check=False,
|
|
60
|
+
)
|
|
61
|
+
return result.returncode == 0
|
|
62
|
+
except FileNotFoundError:
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _create_initial_commit(project_root: Path) -> bool:
|
|
67
|
+
"""Create an initial git commit with the scaffolded files.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
project_root: The project root directory.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if successful, False otherwise.
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
add_result = subprocess.run(
|
|
77
|
+
["git", "add", "."],
|
|
78
|
+
cwd=project_root,
|
|
79
|
+
capture_output=True,
|
|
80
|
+
text=True,
|
|
81
|
+
check=False,
|
|
82
|
+
)
|
|
83
|
+
if add_result.returncode != 0:
|
|
84
|
+
logger.warning(f"git add failed: {add_result.stderr}")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
commit_result = subprocess.run(
|
|
88
|
+
["git", "commit", "-m", "Initial commit: Ralph workflow setup"],
|
|
89
|
+
cwd=project_root,
|
|
90
|
+
capture_output=True,
|
|
91
|
+
text=True,
|
|
92
|
+
check=False,
|
|
93
|
+
)
|
|
94
|
+
# Return code 1 with "nothing to commit" is not an error
|
|
95
|
+
if commit_result.returncode != 0 and "nothing to commit" not in commit_result.stdout:
|
|
96
|
+
logger.warning(f"git commit failed: {commit_result.stderr}")
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
return True
|
|
100
|
+
except FileNotFoundError:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def init(
|
|
105
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"),
|
|
106
|
+
skip_claude: bool = typer.Option(
|
|
107
|
+
False, "--skip-claude", help="Skip invoking Claude Code /init"
|
|
108
|
+
),
|
|
109
|
+
project_name: str | None = typer.Option(
|
|
110
|
+
None, "--name", "-n", help="Project name (defaults to directory name)"
|
|
111
|
+
),
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Scaffold a project for Ralph workflow.
|
|
114
|
+
|
|
115
|
+
Creates plans/ directory with SPEC.md, TASKS.json, and PROGRESS.txt.
|
|
116
|
+
Also creates CLAUDE.md and AGENTS.md with project-specific defaults.
|
|
117
|
+
"""
|
|
118
|
+
project_root = Path.cwd()
|
|
119
|
+
|
|
120
|
+
existing_files = _check_existing_files(project_root)
|
|
121
|
+
if existing_files and not force:
|
|
122
|
+
print_warning("Ralph workflow files already exist:")
|
|
123
|
+
for file in existing_files:
|
|
124
|
+
console.print(f" - {file}")
|
|
125
|
+
console.print()
|
|
126
|
+
console.print("Use [bold]--force[/bold] to overwrite existing files.")
|
|
127
|
+
raise typer.Exit(1)
|
|
128
|
+
|
|
129
|
+
initialized_git = False
|
|
130
|
+
if not _is_git_repo(project_root):
|
|
131
|
+
console.print("[bold]Initializing git repository...[/bold]")
|
|
132
|
+
if _init_git_repo(project_root):
|
|
133
|
+
print_success("Initialized git repository")
|
|
134
|
+
initialized_git = True
|
|
135
|
+
else:
|
|
136
|
+
print_warning("Could not initialize git repository. Git may not be installed.")
|
|
137
|
+
console.print()
|
|
138
|
+
|
|
139
|
+
scaffold = ScaffoldService(project_root=project_root)
|
|
140
|
+
project_type = scaffold.detect_project_type()
|
|
141
|
+
|
|
142
|
+
if project_type != ProjectType.UNKNOWN:
|
|
143
|
+
console.print(f"Detected project type: [bold cyan]{project_type.value}[/bold cyan]")
|
|
144
|
+
else:
|
|
145
|
+
print_warning("Could not detect project type. Using generic template.")
|
|
146
|
+
|
|
147
|
+
# Check for existing PRD content BEFORE scaffolding (scaffolding may overwrite it)
|
|
148
|
+
prd_path = project_root / "plans" / "SPEC.md"
|
|
149
|
+
had_prd_content_before = _has_prd_content(prd_path)
|
|
150
|
+
|
|
151
|
+
console.print()
|
|
152
|
+
console.print("[bold]Creating Ralph workflow files...[/bold]")
|
|
153
|
+
|
|
154
|
+
# Skip CHANGELOG.md creation if it already exists (it's persistent memory)
|
|
155
|
+
changelog_existed = (project_root / "CHANGELOG.md").exists()
|
|
156
|
+
created_files = scaffold.scaffold_all(
|
|
157
|
+
project_name=project_name, skip_changelog=changelog_existed
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
for file_type, path in created_files.items():
|
|
161
|
+
if file_type == "plans_dir":
|
|
162
|
+
continue
|
|
163
|
+
relative_path = path.relative_to(project_root)
|
|
164
|
+
print_success(f"Created {relative_path}")
|
|
165
|
+
|
|
166
|
+
if changelog_existed:
|
|
167
|
+
console.print("[dim] Skipped CHANGELOG.md (already exists)[/dim]")
|
|
168
|
+
|
|
169
|
+
if not had_prd_content_before:
|
|
170
|
+
_handle_missing_prd(prd_path, project_root, skip_claude=skip_claude)
|
|
171
|
+
|
|
172
|
+
if not skip_claude:
|
|
173
|
+
console.print()
|
|
174
|
+
console.print("[bold]Invoking Claude Code /init to enhance project files...[/bold]")
|
|
175
|
+
console.print(
|
|
176
|
+
"[dim]This will analyze your project and update CLAUDE.md and AGENTS.md.[/dim]"
|
|
177
|
+
)
|
|
178
|
+
console.print()
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
claude = ClaudeService(working_dir=project_root)
|
|
182
|
+
exit_code = claude.run_interactive(
|
|
183
|
+
"/init",
|
|
184
|
+
skip_permissions=True,
|
|
185
|
+
append_system_prompt=ClaudeService.AUTONOMOUS_MODE_PROMPT,
|
|
186
|
+
)
|
|
187
|
+
if exit_code != 0:
|
|
188
|
+
print_warning("Claude Code /init completed with non-zero exit code.")
|
|
189
|
+
except (OSError, ClaudeError) as e:
|
|
190
|
+
print_warning(f"Failed to run Claude Code /init: {e}")
|
|
191
|
+
console.print("[dim]You can run 'claude /init' manually later.[/dim]")
|
|
192
|
+
|
|
193
|
+
if initialized_git:
|
|
194
|
+
console.print()
|
|
195
|
+
console.print("[bold]Creating initial commit...[/bold]")
|
|
196
|
+
if _create_initial_commit(project_root):
|
|
197
|
+
print_success("Created initial commit: 'Initial commit: Ralph workflow setup'")
|
|
198
|
+
else:
|
|
199
|
+
print_warning("Could not create initial commit. You may need to commit manually.")
|
|
200
|
+
|
|
201
|
+
console.print()
|
|
202
|
+
print_success("[bold]Ralph workflow initialized![/bold]")
|
|
203
|
+
console.print()
|
|
204
|
+
console.print("[bold]Next steps:[/bold]")
|
|
205
|
+
console.print(" 1. Edit [cyan]plans/SPEC.md[/cyan] with your feature specification")
|
|
206
|
+
console.print(" Or run [cyan]ralph prd[/cyan] to create one interactively")
|
|
207
|
+
console.print()
|
|
208
|
+
console.print(" 2. Generate tasks from your spec:")
|
|
209
|
+
console.print(" [cyan]ralph tasks plans/SPEC.md[/cyan]")
|
|
210
|
+
console.print()
|
|
211
|
+
console.print(" 3. Start the autonomous iteration loop:")
|
|
212
|
+
console.print(" [cyan]ralph loop[/cyan]")
|
|
213
|
+
console.print()
|
|
214
|
+
console.print("[dim]Tip: Review CLAUDE.md to customize quality checks for your project.[/dim]")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _check_existing_files(project_root: Path) -> list[str]:
|
|
218
|
+
"""Check for existing Ralph workflow files.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
project_root: The project root directory.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
List of relative paths to existing files.
|
|
225
|
+
"""
|
|
226
|
+
files_to_check = [
|
|
227
|
+
"plans/SPEC.md",
|
|
228
|
+
"plans/TASKS.json",
|
|
229
|
+
"plans/PROGRESS.txt",
|
|
230
|
+
"CLAUDE.md",
|
|
231
|
+
"AGENTS.md",
|
|
232
|
+
"CHANGELOG.md",
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
return [f for f in files_to_check if (project_root / f).exists()]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _has_prd_content(prd_path: Path) -> bool:
|
|
239
|
+
"""Check if the PRD file has meaningful content beyond the template.
|
|
240
|
+
|
|
241
|
+
The scaffolded SPEC.md template contains placeholder brackets like
|
|
242
|
+
`[Describe the feature...]` and `[Goal 1]`. This function checks if
|
|
243
|
+
the user has replaced these placeholders with actual content.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
prd_path: Path to the PRD file (plans/SPEC.md).
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
True if the file has meaningful content, False otherwise.
|
|
250
|
+
"""
|
|
251
|
+
if not prd_path.exists():
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
content = prd_path.read_text().strip()
|
|
255
|
+
|
|
256
|
+
# Empty file has no content
|
|
257
|
+
if not content:
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
# The scaffold template uses placeholder markers in brackets like:
|
|
261
|
+
# [Describe the feature or project you want to build]
|
|
262
|
+
# [Goal 1], [Goal 2], [Requirement 1], etc.
|
|
263
|
+
# If the file still contains these, it hasn't been filled in
|
|
264
|
+
placeholder_patterns = [
|
|
265
|
+
"[Describe the feature",
|
|
266
|
+
"[Goal 1]",
|
|
267
|
+
"[Requirement 1]",
|
|
268
|
+
"[What this feature will NOT do]",
|
|
269
|
+
"[Describe the high-level architecture]",
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
# Check if any scaffold placeholder patterns are still present
|
|
273
|
+
has_placeholders = any(pattern in content for pattern in placeholder_patterns)
|
|
274
|
+
|
|
275
|
+
# If it has placeholders, it's still template content
|
|
276
|
+
if has_placeholders:
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
# Also check for explicit template comment markers
|
|
280
|
+
template_markers = [
|
|
281
|
+
"<!-- Replace this",
|
|
282
|
+
"[Your feature",
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
if any(marker in content for marker in template_markers):
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
# Check for actual content: must have at least one section heading followed
|
|
289
|
+
# by actual text (not just another heading or placeholder)
|
|
290
|
+
lines = content.split("\n")
|
|
291
|
+
for i, line in enumerate(lines):
|
|
292
|
+
if line.startswith("## "): # Section heading
|
|
293
|
+
# Check if there's actual content after this heading
|
|
294
|
+
for remaining in lines[i + 1 :]:
|
|
295
|
+
stripped = remaining.strip()
|
|
296
|
+
# Skip empty lines and subheadings
|
|
297
|
+
if not stripped or stripped.startswith("#"):
|
|
298
|
+
continue
|
|
299
|
+
# Found actual content line
|
|
300
|
+
# Make sure it's not just a placeholder bracket
|
|
301
|
+
if stripped.startswith("[") and stripped.endswith("]"):
|
|
302
|
+
continue
|
|
303
|
+
# Found real content
|
|
304
|
+
return True
|
|
305
|
+
break
|
|
306
|
+
|
|
307
|
+
return False
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _handle_missing_prd(prd_path: Path, project_root: Path, *, skip_claude: bool = False) -> None:
|
|
311
|
+
"""Handle the case when PRD is missing or empty.
|
|
312
|
+
|
|
313
|
+
Prompts the user to create a PRD using the prd command, or continues
|
|
314
|
+
without one if they decline.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
prd_path: Path to the PRD file (plans/SPEC.md).
|
|
318
|
+
project_root: Path to the project root directory.
|
|
319
|
+
skip_claude: If True, skip the interactive prompt and continue without PRD.
|
|
320
|
+
"""
|
|
321
|
+
console.print()
|
|
322
|
+
print_warning("No PRD found at plans/SPEC.md")
|
|
323
|
+
console.print()
|
|
324
|
+
console.print(
|
|
325
|
+
"[dim]A PRD (Product Requirements Document) helps Claude Code understand "
|
|
326
|
+
"your project goals.[/dim]"
|
|
327
|
+
)
|
|
328
|
+
console.print()
|
|
329
|
+
|
|
330
|
+
# In non-interactive mode, skip the prompt and continue without PRD
|
|
331
|
+
if skip_claude:
|
|
332
|
+
console.print(
|
|
333
|
+
"[dim]Skipping PRD creation (--skip-claude mode). "
|
|
334
|
+
"You can create one later with 'ralph prd' or edit plans/SPEC.md directly.[/dim]"
|
|
335
|
+
)
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
if Confirm.ask("Would you like to create a PRD first?", default=True):
|
|
339
|
+
console.print()
|
|
340
|
+
console.print("[bold]Launching PRD creation...[/bold]")
|
|
341
|
+
console.print()
|
|
342
|
+
try:
|
|
343
|
+
# Invoke the prd command to create the specification
|
|
344
|
+
# Use the default output path which is plans/SPEC.md
|
|
345
|
+
# Pass all parameters explicitly to avoid Typer Option defaults not being applied
|
|
346
|
+
prd_command(output=Path("plans/SPEC.md"), verbose=False, input_text=None, file=None)
|
|
347
|
+
except typer.Exit:
|
|
348
|
+
# PRD command completed (either successfully or user cancelled)
|
|
349
|
+
pass
|
|
350
|
+
except (OSError, ClaudeError) as e:
|
|
351
|
+
print_warning(f"PRD creation failed: {e}")
|
|
352
|
+
console.print("[dim]You can create a PRD later with 'ralph prd'.[/dim]")
|
|
353
|
+
else:
|
|
354
|
+
console.print()
|
|
355
|
+
console.print(
|
|
356
|
+
"[dim]Proceeding without PRD. You can create one later with "
|
|
357
|
+
"'ralph prd' or edit plans/SPEC.md directly.[/dim]"
|
|
358
|
+
)
|